diff --git a/.bingo/OWNERS b/.bingo/OWNERS new file mode 100644 index 0000000000..835cabe507 --- /dev/null +++ b/.bingo/OWNERS @@ -0,0 +1,2 @@ +approvers: + - ci-approvers diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index ea22690e3f..fe814c5176 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -23,11 +23,17 @@ $(BINGO): $(BINGO_DIR)/bingo.mod @echo "(re)installing $(GOBIN)/bingo-v0.9.0" @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=bingo.mod -o=$(GOBIN)/bingo-v0.9.0 "github.com/bwplotka/bingo" -CONTROLLER_GEN := $(GOBIN)/controller-gen-v0.16.1 +CONTROLLER_GEN := $(GOBIN)/controller-gen-v0.19.0 $(CONTROLLER_GEN): $(BINGO_DIR)/controller-gen.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/controller-gen-v0.16.1" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=controller-gen.mod -o=$(GOBIN)/controller-gen-v0.16.1 "sigs.k8s.io/controller-tools/cmd/controller-gen" + @echo "(re)installing $(GOBIN)/controller-gen-v0.19.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=controller-gen.mod -o=$(GOBIN)/controller-gen-v0.19.0 "sigs.k8s.io/controller-tools/cmd/controller-gen" + +CRD_DIFF := $(GOBIN)/crd-diff-v0.2.0 +$(CRD_DIFF): $(BINGO_DIR)/crd-diff.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/crd-diff-v0.2.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=crd-diff.mod -o=$(GOBIN)/crd-diff-v0.2.0 "github.com/everettraven/crd-diff" CRD_REF_DOCS := $(GOBIN)/crd-ref-docs-v0.1.0 $(CRD_REF_DOCS): $(BINGO_DIR)/crd-ref-docs.mod @@ -35,11 +41,17 @@ $(CRD_REF_DOCS): $(BINGO_DIR)/crd-ref-docs.mod @echo "(re)installing $(GOBIN)/crd-ref-docs-v0.1.0" @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=crd-ref-docs.mod -o=$(GOBIN)/crd-ref-docs-v0.1.0 "github.com/elastic/crd-ref-docs" -GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.60.3 +GOJQ := $(GOBIN)/gojq-v0.12.17 +$(GOJQ): $(BINGO_DIR)/gojq.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/gojq-v0.12.17" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=gojq.mod -o=$(GOBIN)/gojq-v0.12.17 "github.com/itchyny/gojq/cmd/gojq" + +GOLANGCI_LINT := $(GOBIN)/golangci-lint-v2.1.6 $(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/golangci-lint-v1.60.3" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.60.3 "github.com/golangci/golangci-lint/cmd/golangci-lint" + @echo "(re)installing $(GOBIN)/golangci-lint-v2.1.6" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v2.1.6 "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" GORELEASER := $(GOBIN)/goreleaser-v1.26.2 $(GORELEASER): $(BINGO_DIR)/goreleaser.mod @@ -47,33 +59,45 @@ $(GORELEASER): $(BINGO_DIR)/goreleaser.mod @echo "(re)installing $(GOBIN)/goreleaser-v1.26.2" @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=goreleaser.mod -o=$(GOBIN)/goreleaser-v1.26.2 "github.com/goreleaser/goreleaser" -KIND := $(GOBIN)/kind-v0.24.0 +HELM := $(GOBIN)/helm-v3.18.4 +$(HELM): $(BINGO_DIR)/helm.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/helm-v3.18.4" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=helm.mod -o=$(GOBIN)/helm-v3.18.4 "helm.sh/helm/v3/cmd/helm" + +KIND := $(GOBIN)/kind-v0.30.0 $(KIND): $(BINGO_DIR)/kind.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/kind-v0.24.0" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kind.mod -o=$(GOBIN)/kind-v0.24.0 "sigs.k8s.io/kind" + @echo "(re)installing $(GOBIN)/kind-v0.30.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kind.mod -o=$(GOBIN)/kind-v0.30.0 "sigs.k8s.io/kind" -KUSTOMIZE := $(GOBIN)/kustomize-v4.5.7 +KUSTOMIZE := $(GOBIN)/kustomize-v5.6.0 $(KUSTOMIZE): $(BINGO_DIR)/kustomize.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/kustomize-v4.5.7" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kustomize.mod -o=$(GOBIN)/kustomize-v4.5.7 "sigs.k8s.io/kustomize/kustomize/v4" + @echo "(re)installing $(GOBIN)/kustomize-v5.6.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=kustomize.mod -o=$(GOBIN)/kustomize-v5.6.0 "sigs.k8s.io/kustomize/kustomize/v5" -OPERATOR_SDK := $(GOBIN)/operator-sdk-v1.36.1 +OPERATOR_SDK := $(GOBIN)/operator-sdk-v1.39.1 $(OPERATOR_SDK): $(BINGO_DIR)/operator-sdk.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/operator-sdk-v1.36.1" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -ldflags=-X=github.com/operator-framework/operator-sdk/internal/version.Version=v1.34.1 -mod=mod -modfile=operator-sdk.mod -o=$(GOBIN)/operator-sdk-v1.36.1 "github.com/operator-framework/operator-sdk/cmd/operator-sdk" + @echo "(re)installing $(GOBIN)/operator-sdk-v1.39.1" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -ldflags=-X=github.com/operator-framework/operator-sdk/internal/version.Version=v1.34.1 -mod=mod -modfile=operator-sdk.mod -o=$(GOBIN)/operator-sdk-v1.39.1 "github.com/operator-framework/operator-sdk/cmd/operator-sdk" -OPM := $(GOBIN)/opm-v1.46.0 +OPM := $(GOBIN)/opm-v1.51.0 $(OPM): $(BINGO_DIR)/opm.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/opm-v1.46.0" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=opm.mod -o=$(GOBIN)/opm-v1.46.0 "github.com/operator-framework/operator-registry/cmd/opm" + @echo "(re)installing $(GOBIN)/opm-v1.51.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=opm.mod -o=$(GOBIN)/opm-v1.51.0 "github.com/operator-framework/operator-registry/cmd/opm" -SETUP_ENVTEST := $(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6 +SETUP_ENVTEST := $(GOBIN)/setup-envtest-v0.0.0-20250620151452-b9a9ca01fd37 $(SETUP_ENVTEST): $(BINGO_DIR)/setup-envtest.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=setup-envtest.mod -o=$(GOBIN)/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6 "sigs.k8s.io/controller-runtime/tools/setup-envtest" + @echo "(re)installing $(GOBIN)/setup-envtest-v0.0.0-20250620151452-b9a9ca01fd37" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=setup-envtest.mod -o=$(GOBIN)/setup-envtest-v0.0.0-20250620151452-b9a9ca01fd37 "sigs.k8s.io/controller-runtime/tools/setup-envtest" + +YAMLFMT := $(GOBIN)/yamlfmt-v0.20.0 +$(YAMLFMT): $(BINGO_DIR)/yamlfmt.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/yamlfmt-v0.20.0" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=yamlfmt.mod -o=$(GOBIN)/yamlfmt-v0.20.0 "github.com/google/yamlfmt/cmd/yamlfmt" diff --git a/.bingo/controller-gen.mod b/.bingo/controller-gen.mod index 2d0082065f..3c92fc0a00 100644 --- a/.bingo/controller-gen.mod +++ b/.bingo/controller-gen.mod @@ -1,7 +1,7 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.22.0 +go 1.24.0 -toolchain go1.22.2 +toolchain go1.24.3 -require sigs.k8s.io/controller-tools v0.16.1 // cmd/controller-gen +require sigs.k8s.io/controller-tools v0.19.0 // cmd/controller-gen diff --git a/.bingo/controller-gen.sum b/.bingo/controller-gen.sum index 6d5c76c091..3bb68478c8 100644 --- a/.bingo/controller-gen.sum +++ b/.bingo/controller-gen.sum @@ -2,6 +2,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= @@ -12,8 +14,12 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= @@ -24,12 +30,28 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +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.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +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/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -40,10 +62,18 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/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/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -60,6 +90,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -71,8 +103,14 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 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/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/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= @@ -85,6 +123,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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= @@ -100,6 +142,14 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -114,6 +164,14 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -121,6 +179,14 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -138,6 +204,14 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= @@ -148,6 +222,14 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -162,11 +244,24 @@ golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 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= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -185,6 +280,16 @@ k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= +k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apiextensions-apiserver v0.27.1 h1:Hp7B3KxKHBZ/FxmVFVpaDiXI6CCSr49P1OJjxKO6o4g= @@ -195,6 +300,16 @@ k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= +k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= +k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= +k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= +k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc= @@ -205,6 +320,24 @@ k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= +k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= +k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/code-generator v0.33.0 h1:B212FVl6EFqNmlgdOZYWNi77yBv+ed3QgQsMR8YQCw4= +k8s.io/code-generator v0.33.0/go.mod h1:KnJRokGxjvbBQkSJkbVuBbu6z4B0rC7ynkpY5Aw6m9o= +k8s.io/code-generator v0.34.0 h1:Ze2i1QsvUprIlX3oHiGv09BFQRLCz+StA8qKwwFzees= +k8s.io/code-generator v0.34.0/go.mod h1:Py2+4w2HXItL8CGhks8uI/wS3Y93wPKO/9mBQUYNua0= +k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= +k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= +k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -216,6 +349,10 @@ k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= @@ -224,6 +361,10 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSn k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-tools v0.10.0 h1:0L5DTDTFB67jm9DkfrONgTGmfc/zYow0ZaHyppizU2U= sigs.k8s.io/controller-tools v0.10.0/go.mod h1:uvr0EW6IsprfB0jpQq6evtKy+hHyHCXNfdWI5ONPx94= sigs.k8s.io/controller-tools v0.12.0 h1:TY6CGE6+6hzO7hhJFte65ud3cFmmZW947jajXkuDfBw= @@ -234,15 +375,40 @@ sigs.k8s.io/controller-tools v0.15.0 h1:4dxdABXGDhIa68Fiwaif0vcu32xfwmgQ+w8p+5Cx sigs.k8s.io/controller-tools v0.15.0/go.mod h1:8zUSS2T8Hx0APCNRhJWbS3CAQEbIxLa07khzh7pZmXM= sigs.k8s.io/controller-tools v0.16.1 h1:gvIsZm+2aimFDIBiDKumR7EBkc+oLxljoUVfRbDI6RI= sigs.k8s.io/controller-tools v0.16.1/go.mod h1:0I0xqjR65YTfoO12iR+mZR6s6UAVcUARgXRlsu0ljB0= +sigs.k8s.io/controller-tools v0.17.1 h1:bQ+dKCS7jY9AgpefenBDtm6geJZCHVKbegpLynxgyus= +sigs.k8s.io/controller-tools v0.17.1/go.mod h1:3QXAdrmdxYuQ4MifvbCAFD9wLXn7jylnfBPYS4yVDdc= +sigs.k8s.io/controller-tools v0.17.2 h1:jNFOKps8WnaRKZU2R+4vRCHnXyJanVmXBWqkuUPFyFg= +sigs.k8s.io/controller-tools v0.17.2/go.mod h1:4q5tZG2JniS5M5bkiXY2/potOiXyhoZVw/U48vLkXk0= +sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3EwfNw= +sigs.k8s.io/controller-tools v0.17.3/go.mod h1:1ii+oXcYZkxcBXzwv3YZBlzjt1fvkrCGjVF73blosJI= +sigs.k8s.io/controller-tools v0.18.0 h1:rGxGZCZTV2wJreeRgqVoWab/mfcumTMmSwKzoM9xrsE= +sigs.k8s.io/controller-tools v0.18.0/go.mod h1:gLKoiGBriyNh+x1rWtUQnakUYEujErjXs9pf+x/8n1U= +sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= +sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 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/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 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/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/.bingo/crd-diff.mod b/.bingo/crd-diff.mod new file mode 100644 index 0000000000..41d7382088 --- /dev/null +++ b/.bingo/crd-diff.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.22.5 + +require github.com/everettraven/crd-diff v0.2.0 diff --git a/.bingo/crd-diff.sum b/.bingo/crd-diff.sum new file mode 100644 index 0000000000..9d915a42ba --- /dev/null +++ b/.bingo/crd-diff.sum @@ -0,0 +1,327 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= +github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/everettraven/crd-diff v0.0.0-20241112183958-25304aa59cdb h1:I8H7/ZAvoHNKedyOaTsh9Y+YgcysmOAMPaaqx5xKMiU= +github.com/everettraven/crd-diff v0.0.0-20241112183958-25304aa59cdb/go.mod h1:sB0TZKVM9DVyC1zKHfJtb7VOMvst8gr0ETM4KsJ3gPA= +github.com/everettraven/crd-diff v0.1.0 h1:wlaA+USeSpQSwzjF/cxl5b+vPZygxxmvnbm3NGyn2vs= +github.com/everettraven/crd-diff v0.1.0/go.mod h1:sB0TZKVM9DVyC1zKHfJtb7VOMvst8gr0ETM4KsJ3gPA= +github.com/everettraven/crd-diff v0.2.0 h1:72tY1p+eHIYaGORYmrKO8AxcDRowaOn75eQ/lhDMoPQ= +github.com/everettraven/crd-diff v0.2.0/go.mod h1:sB0TZKVM9DVyC1zKHfJtb7VOMvst8gr0ETM4KsJ3gPA= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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.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/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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +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/openshift/crd-schema-checker v0.0.0-20241014171011-8425fdfe9988 h1:K/URE0cbSqv27EkbpPXGMu1vC78C0WmnHhO1Lx8Hzzk= +github.com/openshift/crd-schema-checker v0.0.0-20241014171011-8425fdfe9988/go.mod h1:sTxJ4ZFW9r9fEdbW2v0yMRi6NcyTbx0fII4p83IQ+L8= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +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.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +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-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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20210615035016-665e8c7367d1/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +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= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/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= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= +k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU= +sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU= +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/.bingo/gojq.mod b/.bingo/gojq.mod new file mode 100644 index 0000000000..004aae3b13 --- /dev/null +++ b/.bingo/gojq.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.24.4 + +require github.com/itchyny/gojq v0.12.17 // cmd/gojq diff --git a/.bingo/gojq.sum b/.bingo/gojq.sum new file mode 100644 index 0000000000..e87b5b0e34 --- /dev/null +++ b/.bingo/gojq.sum @@ -0,0 +1,17 @@ +github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg= +github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.bingo/golangci-lint.mod b/.bingo/golangci-lint.mod index 25f0891d94..07ecc9aa88 100644 --- a/.bingo/golangci-lint.mod +++ b/.bingo/golangci-lint.mod @@ -1,7 +1,7 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.22.1 +go 1.24.2 -toolchain go1.22.5 +toolchain go1.24.3 -require github.com/golangci/golangci-lint v1.60.3 // cmd/golangci-lint +require github.com/golangci/golangci-lint/v2 v2.1.6 // cmd/golangci-lint diff --git a/.bingo/golangci-lint.sum b/.bingo/golangci-lint.sum index 5a272b75f5..17881e374b 100644 --- a/.bingo/golangci-lint.sum +++ b/.bingo/golangci-lint.sum @@ -1,13 +1,12 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= 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.44.3/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= @@ -18,9 +17,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV 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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 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= @@ -38,199 +34,134 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo 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= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.2.2 h1:kQeUTkFTaBRtd/7jm8OKJl9iHk0gAO+TDFPHGSna0aw= -github.com/4meepo/tagalign v1.2.2/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= -github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= -github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= -github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= -github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= -github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= -github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= -github.com/Antonboom/errname v0.1.10 h1:RZ7cYo/GuZqjr1nuJLNe8ZH+a+Jd9DaZzttWzak9Bls= -github.com/Antonboom/errname v0.1.10/go.mod h1:xLeiCIrvVNpUtsN0wxAh05bNIZpqE22/qDMnTBTttiA= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= -github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= -github.com/Antonboom/nilnil v0.1.5 h1:X2JAdEVcbPaOom2TUa1FxZ3uyuUlex0XMLGYMemu6l0= -github.com/Antonboom/nilnil v0.1.5/go.mod h1:I24toVuBKhfP5teihGWctrRiPbRKHwZIFOvc6v3HZXk= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/nilnil v0.1.8 h1:97QG7xrLq4TBK2U9aFq/I8Mcgz67pwMIiswnTA9gIn0= -github.com/Antonboom/nilnil v0.1.8/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= -github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= -github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= -github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= -github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= -github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= -github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE= +github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw= +github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng= +github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE= +github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o= +github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= -github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= -github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= -github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= -github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI= +github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= 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/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= -github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= -github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= -github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= +github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/ashanbrown/forbidigo v1.5.3 h1:jfg+fkm/snMx+V9FBwsl1d340BV/99kZGv5jN9hBoXk= -github.com/ashanbrown/forbidigo v1.5.3/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= +github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= -github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= -github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= -github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= -github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= -github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= -github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= -github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= -github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= -github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= -github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= -github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= -github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= -github.com/butuzov/ireturn v0.2.0 h1:kCHi+YzC150GE98WFuZQu9yrTn6GEydO2AuPLbTgnO4= -github.com/butuzov/ireturn v0.2.0/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= -github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= -github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= -github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= +github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= +github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= +github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= +github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= +github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= +github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= +github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0= +github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 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/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= 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/ckaznocha/intrange v0.1.1 h1:gHe4LfqCspWkh8KpJFs20fJz3XRHFBFUV9yI7Itu83Q= -github.com/ckaznocha/intrange v0.1.1/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= -github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= -github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= +github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= +github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= -github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= -github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= -github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= -github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= -github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= +github.com/daixiang0/gci v0.13.6 h1:RKuEOSkGpSadkGbvZ6hJ4ddItT3cVZ9Vn9Rybk6xjl8= +github.com/daixiang0/gci v0.13.6/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= +github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= 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/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= -github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= +github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= -github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/go-critic/go-critic v0.8.1 h1:16omCF1gN3gTzt4j4J6fKI/HnRojhEp+Eks6EuKw3vw= -github.com/go-critic/go-critic v0.8.1/go.mod h1:kpzXl09SIJX1cr9TB/g/sAG+eFEl7ZS9f9cqvZtyNl0= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= -github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= -github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= -github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= -github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= +github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= +github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY= +github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI= 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= @@ -246,7 +177,6 @@ github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4 github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= @@ -259,16 +189,12 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= -github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -298,58 +224,27 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.53.3 h1:CUcRafczT4t1F+mvdkUm6KuOpxUZTl0yWN/rSU6sSMo= -github.com/golangci/golangci-lint v1.53.3/go.mod h1:W4Gg3ONq6p3Jl+0s/h9Gr0j7yEgHJWWZO2bHl2tBUXM= -github.com/golangci/golangci-lint v1.57.2 h1:NNhxfZyL5He1WWDrIvl1a4n5bvWZBcgAqBwlJAAgLTw= -github.com/golangci/golangci-lint v1.57.2/go.mod h1:ApiG3S3Ca23QyfGp5BmsorTiVxJpr5jGiNS0BkdSidg= -github.com/golangci/golangci-lint v1.58.0 h1:r8duFARMJ0VdSM9tDXAdt2+f57dfZQmagvYX6kmkUKQ= -github.com/golangci/golangci-lint v1.58.0/go.mod h1:WAY3BnSLvTUEv41Q0v3ZFzNybLRF+a7Vd9Da8Jx9Eqo= -github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= -github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= -github.com/golangci/golangci-lint v1.60.3 h1:l38A5de24ZeDlcFF+EB7m3W5joPD99/hS5SIHJPyZa0= -github.com/golangci/golangci-lint v1.60.3/go.mod h1:J4vOpcjzRI+lDL2DKNGBZVB3EQSBfCBCMpaydWLtJNo= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= -github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= -github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= -github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w= +github.com/golangci/golangci-lint/v2 v2.1.6/go.mod h1:EPj+fgv4TeeBq3TcqaKZb3vkiV5dP4hHHKhXhEhzci8= +github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= +github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= -github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= -github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= -github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= -github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= -github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= +github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= 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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -364,14 +259,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -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-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.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/martian/v3 v3.1.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= @@ -379,61 +271,44 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf 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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.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/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= -github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.8.1 h1:PPqCYp3K/xlOj5JmIe6O1Mj6r1DbkdbLtR3AJuZo414= +github.com/jgautheron/goconst v1.8.1/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= -github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= -github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= -github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= -github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= -github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= 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= @@ -443,83 +318,65 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 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/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.0.10 h1:8HYDy6KQYqTmD7JuhZMWS1nwPru9889XI24ROd/+WXI= -github.com/karamaru-alpha/copyloopvar v1.0.10/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= -github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= -github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= -github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= -github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= -github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/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/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.7 h1:2uCk94js0+nVNQoHZNLBkAR1DQJrVzw6T0RMzJn55dQ= -github.com/kunwardeep/paralleltest v1.0.7/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= -github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/lasiar/canonicalheader v1.0.6 h1:LJiiZ/MzkqibXOL2v+J8+WZM21pM0ivrBY/jbm9f5fo= -github.com/lasiar/canonicalheader v1.0.6/go.mod h1:GfXTLQb3O1qF5qcSTyXTnfNUggUNyzbkOSpzZ0dpUJo= -github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= -github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= -github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= -github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= -github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8= +github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/exptostd v0.4.3 h1:Ag1aGiq2epGePuRJhez2mzOpZ8sI9Gimcb4Sb3+pk9Y= +github.com/ldez/exptostd v0.4.3/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.3 h1:pJpN0x3fMupdTf/IapYjnkhiY1nSTN+pox1/GyBRw3k= +github.com/ldez/usetesting v0.4.3/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= -github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= +github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manuelarte/funcorder v0.2.1 h1:7QJsw3qhljoZ5rH0xapIvjw31EcQeFbF31/7kQ/xS34= +github.com/manuelarte/funcorder v0.2.1/go.mod h1:BQQ0yW57+PF9ZpjpeJDKOffEsQbxDFKW8F8zSMe/Zd0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.3.2 h1:Wb8NQKBaALBJ3xrrj4zpwJwqwNA6nDpyJSEQWcCka6U= -github.com/mgechev/revive v1.3.2/go.mod h1:UCLtc7o5vg5aXCwdUTU1kEBQ1v+YXPAkYDIDXbrs5I0= -github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= -github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= -github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= -github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= +github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM= +github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo= 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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -529,26 +386,20 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 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/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= -github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.12.1 h1:vwOqb5Nu05OikTXqhvLdHCGcx5uthIYIl0t79UVrERQ= -github.com/nunnatsa/ginkgolinter v0.12.1/go.mod h1:AK8Ab1PypVrcGUusuKD8RDcl2KgsIwvNaaxAlyHSzso= -github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= -github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -558,28 +409,15 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/polyfloyd/go-errorlint v1.4.2 h1:CU+O4181IxFDdPH6t/HT7IiDj1I7zxNi1RIUxYwn8d0= -github.com/polyfloyd/go-errorlint v1.4.2/go.mod h1:k6fU/+fQe38ednoZS51T7gSIGQW1y94d6TkSr35OzH8= -github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= -github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= -github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKUelO3RBo= -github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= -github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= -github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= -github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= -github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= +github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= +github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= 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= @@ -602,10 +440,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc= -github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw= -github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= -github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ= +github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= @@ -614,44 +450,29 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= -github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= -github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= -github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= -github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= -github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= -github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= -github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= -github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= +github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= +github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1jbEOPrEhDzGYq0= -github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= -github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= -github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= -github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= -github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U= -github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= -github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= -github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= -github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0 h1:VqD4JMoqwuuCz8GZlBDsIDyE6K4YUsWJpbNtuOWHoFk= -github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0/go.mod h1:iyeMMRw8QEmueUSZ2VqmkQMiDyDcobfPnG00CV/NWdE= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc= +github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -661,44 +482,33 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= -github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= -github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= -github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -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/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= @@ -706,63 +516,41 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= -github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= -github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= -github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= -github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= -github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= -github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= -github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= +github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= +github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= +github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= +github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU= +github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= -github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= -github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= -github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= -github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= -github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= -github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= -github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= -github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= -github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/xen0n/gosmopolitan v1.2.1 h1:3pttnTuFumELBRSh+KQs1zcz4fN6Zy7aB0xlnQSn1Iw= -github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAGAsElF8xBQA= -github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= -github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= +github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= -github.com/ykadowak/zerologlint v0.1.2 h1:Um4P5RMmelfjQqQJKtE8ZW+dLZrXrENeIzWWKw800U4= -github.com/ykadowak/zerologlint v0.1.2/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -772,38 +560,23 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= -gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= -gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= -gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= -go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= -go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= -go-simpler.org/musttag v0.12.1 h1:yaMcjl/uyVnd1z6GqIhBiFH/PoqNN9f2IgtU7bp7W/0= -go-simpler.org/musttag v0.12.1/go.mod h1:46HKu04A3Am9Lne5kKP0ssgwY3AeIlqsDzz3UxKROpY= -go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= -go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= -go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= -go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= -go-simpler.org/sloglint v0.6.0 h1:0YcqSVG7LI9EVBfRPhgPec79BH6X6mwjFuUR5Mr7j1M= -go-simpler.org/sloglint v0.6.0/go.mod h1:+kJJtebtPePWyG5boFwY46COydAggADDOHM22zOvzBk= -go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= -go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= -go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= -go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= +go-simpler.org/musttag v0.13.1 h1:lw2sJyu7S1X8lc8zWUAdH42y+afdcCnHhWpnkWvd6vU= +go-simpler.org/musttag v0.13.1/go.mod h1:8r450ehpMLQgvpb6sg+hV5Ur47eH6olp/3yEanfG97k= +go-simpler.org/sloglint v0.11.0 h1:JlR1X4jkbeaffiyjLtymeqmGDKBDO1ikC6rjiuFAOco= +go-simpler.org/sloglint v0.11.0/go.mod h1:CFDO8R1i77dlciGfPEPvYke2ZMx4eyGiEIWkyeW2Pvw= +go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk= +go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s= 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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.tmz.dev/musttag v0.7.0 h1:QfytzjTWGXZmChoX0L++7uQN+yRCPfyFm+whsM+lfGc= -go.tmz.dev/musttag v0.7.0/go.mod h1:oTFPvgOkJmp5kYL02S8+jrH0eLrBIl57rzWeA26zDEM= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -814,10 +587,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U 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/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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= @@ -828,18 +600,11 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 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= @@ -852,7 +617,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl 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/lint v0.0.0-20201208152925-83fdc39ff7b5/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= @@ -861,25 +625,17 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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= @@ -909,27 +665,22 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -944,14 +695,10 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -983,17 +730,11 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= @@ -1002,50 +743,38 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220702020025-31831981b65f/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.4/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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= @@ -1055,7 +784,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 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-20190321232350-e250d351ecad/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= @@ -1063,10 +791,8 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190910044552-dd2b5c81c578/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-20191108193012-7d206e10da11/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= @@ -1096,37 +822,20 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -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/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 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= @@ -1147,16 +856,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M 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.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 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/appengine v1.6.7/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= @@ -1186,13 +891,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc 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 v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 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= @@ -1205,10 +903,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa 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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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= @@ -1221,12 +915,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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= @@ -1251,30 +941,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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= -honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= -honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= -honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= -honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= -mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= -mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= -mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= -mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= -mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= +mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= +mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= +mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= 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= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= \ No newline at end of file diff --git a/.bingo/helm.mod b/.bingo/helm.mod new file mode 100644 index 0000000000..5c54ed4210 --- /dev/null +++ b/.bingo/helm.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.24.3 + +require helm.sh/helm/v3 v3.18.4 // cmd/helm diff --git a/.bingo/helm.sum b/.bingo/helm.sum new file mode 100644 index 0000000000..4477f0392d --- /dev/null +++ b/.bingo/helm.sum @@ -0,0 +1,303 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= +github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +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.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +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.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +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-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +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/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/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.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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +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.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= +helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ= +helm.sh/helm/v3 v3.18.4/go.mod h1:WVnwKARAw01iEdjpEkP7Ii1tT1pTPYfM1HsakFKM3LI= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y= +k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y= +k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= +sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +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/.bingo/kind.mod b/.bingo/kind.mod index d1f63e6568..becf46c7b7 100644 --- a/.bingo/kind.mod +++ b/.bingo/kind.mod @@ -2,4 +2,4 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT go 1.20 -require sigs.k8s.io/kind v0.24.0 +require sigs.k8s.io/kind v0.30.0 diff --git a/.bingo/kind.sum b/.bingo/kind.sum index 1e790904a4..af37989b32 100644 --- a/.bingo/kind.sum +++ b/.bingo/kind.sum @@ -1,3 +1,5 @@ +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= @@ -40,6 +42,8 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= @@ -58,6 +62,14 @@ sigs.k8s.io/kind v0.22.0 h1:z/+yr/azoOfzsfooqRsPw1wjJlqT/ukXP0ShkHwNlsI= sigs.k8s.io/kind v0.22.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= sigs.k8s.io/kind v0.24.0 h1:g4y4eu0qa+SCeKESLpESgMmVFBebL0BDa6f777OIWrg= sigs.k8s.io/kind v0.24.0/go.mod h1:t7ueEpzPYJvHA8aeLtI52rtFftNgUYUaCwvxjk7phfw= +sigs.k8s.io/kind v0.26.0 h1:8fS6I0Q5WGlmLprSpH0DarlOSdcsv0txnwc93J2BP7M= +sigs.k8s.io/kind v0.26.0/go.mod h1:t7ueEpzPYJvHA8aeLtI52rtFftNgUYUaCwvxjk7phfw= +sigs.k8s.io/kind v0.27.0 h1:PQ3f0iAWNIj66LYkZ1ivhEg/+Zb6UPMbO+qVei/INZA= +sigs.k8s.io/kind v0.27.0/go.mod h1:RZVFmy6qcwlSWwp6xeIUv7kXCPF3i8MXsEXxW/J+gJY= +sigs.k8s.io/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI= +sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o= +sigs.k8s.io/kind v0.30.0 h1:2Xi1KFEfSMm0XDcvKnUt15ZfgRPCT0OnCBbpgh8DztY= +sigs.k8s.io/kind v0.30.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/.bingo/kustomize.mod b/.bingo/kustomize.mod index 0813d44b62..e5026f2ff3 100644 --- a/.bingo/kustomize.mod +++ b/.bingo/kustomize.mod @@ -1,5 +1,5 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.20 +go 1.23.4 -require sigs.k8s.io/kustomize/kustomize/v4 v4.5.7 +require sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 diff --git a/.bingo/kustomize.sum b/.bingo/kustomize.sum index 03de64e7e3..771b9dd5ae 100644 --- a/.bingo/kustomize.sum +++ b/.bingo/kustomize.sum @@ -1,230 +1,85 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +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.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +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/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 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.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -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-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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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= -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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -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.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.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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 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.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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661 h1:nqYOUleKLC/0P1zbU29F5q6aoezM6MOAVz+iyfQbZ5M= -k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= -sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/cmd/config v0.10.9 h1:LV8AUwZPuvqhGfia50uNwsPwNg1xOy9koEf5hyBnYs4= -sigs.k8s.io/kustomize/cmd/config v0.10.9/go.mod h1:T0s850zPV3wKfBALA0dyeP/K74jlJcoP8Pr9ZWwE3MQ= -sigs.k8s.io/kustomize/kustomize/v4 v4.5.7 h1:cDW6AVMl6t/SLuQaezMET8hgnadZGIAr8tUrxFVOrpg= -sigs.k8s.io/kustomize/kustomize/v4 v4.5.7/go.mod h1:VSNKEH9D9d9bLiWEGbS6Xbg/Ih0tgQalmPvntzRxZ/Q= -sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= -sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= +sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= +sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= +sigs.k8s.io/kustomize/cmd/config v0.19.0 h1:D3uASwjHWHmNiEHu3pPJBJMBIsb+auFvHrHql3HAarU= +sigs.k8s.io/kustomize/cmd/config v0.19.0/go.mod h1:29Vvdl26PidPLUDi7nfjYa/I0wHBkwCZp15Nlcc4y98= +sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA= +sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= +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/.bingo/operator-sdk.mod b/.bingo/operator-sdk.mod index 772abcbf16..61f51525cc 100644 --- a/.bingo/operator-sdk.mod +++ b/.bingo/operator-sdk.mod @@ -1,8 +1,6 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.21 - -toolchain go1.21.8 +go 1.23.4 replace github.com/containerd/containerd => github.com/containerd/containerd v1.4.11 @@ -10,4 +8,4 @@ replace github.com/docker/distribution => github.com/docker/distribution v0.0.0- replace github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.10.0 -require github.com/operator-framework/operator-sdk v1.36.1 // cmd/operator-sdk -ldflags=-X=github.com/operator-framework/operator-sdk/internal/version.Version=v1.34.1 +require github.com/operator-framework/operator-sdk v1.39.1 // cmd/operator-sdk -ldflags=-X=github.com/operator-framework/operator-sdk/internal/version.Version=v1.34.1 diff --git a/.bingo/operator-sdk.sum b/.bingo/operator-sdk.sum index 6dd84bc15a..69743e4525 100644 --- a/.bingo/operator-sdk.sum +++ b/.bingo/operator-sdk.sum @@ -49,7 +49,10 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl 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= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -60,6 +63,8 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -71,9 +76,13 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -84,12 +93,16 @@ github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOp github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.12.0-rc.0 h1:wX/F5huJxH9APBkhKSEAqaiZsuBvbbDnyBROZAqsSaY= github.com/Microsoft/hcsshim v0.12.0-rc.0/go.mod h1:rvOnw3YlfoNnEp45wReUngvsXbwRW+AFQ10GVjG1kMU= github.com/Microsoft/hcsshim v0.12.0-rc.1 h1:Hy+xzYujv7urO5wrgcG58SPMOXNLrj4WCJbySs2XX/A= github.com/Microsoft/hcsshim v0.12.0-rc.1/go.mod h1:Y1a1S0QlYp1mBpyvGiuEdOfZqnao+0uX5AWHXQ5NhZU= +github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= +github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -112,6 +125,8 @@ github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrG github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -146,6 +161,8 @@ github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+M github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -153,6 +170,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= @@ -179,6 +198,8 @@ github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/containerd v1.4.11 h1:QCGOUN+i70jEEL/A6JVIbhy4f4fanzAzSR4kNG7SlcE= @@ -187,6 +208,8 @@ github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvA github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= @@ -201,25 +224,35 @@ github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lW github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs= github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= +github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containers/common v0.56.0 h1:hysHUsEai1EkMXanU26UV55wMXns/a6AYmaFqJ4fEMY= github.com/containers/common v0.56.0/go.mod h1:IjaDdfUtcs2CfCcJMZxuut4XlvkTkY9Nlqkso9xCOq4= github.com/containers/common v0.57.1 h1:KWAs4PMPgBFmBV4QNbXhUB8TqvlgR95BJN2sbbXkWHY= github.com/containers/common v0.57.1/go.mod h1:t/Z+/sFrapvFMEJe3YnecN49/Tae2wYEQShbEN6SRaU= +github.com/containers/common v0.60.4 h1:H5+LAMHPZEqX6vVNOQ+IguVsaFl8kbO/SZ/VPXjxhy0= +github.com/containers/common v0.60.4/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= github.com/containers/image/v5 v5.28.0 h1:H4cWbdI88UA/mDb6SxMo3IxpmS1BSs/Kifvhwt9g048= github.com/containers/image/v5 v5.28.0/go.mod h1:9aPnNkwHNHgGl9VlQxXEshvmOJRbdRAc1rNDD6sP2eU= github.com/containers/image/v5 v5.29.0 h1:9+nhS/ZM7c4Kuzu5tJ0NMpxrgoryOJ2HAYTgG8Ny7j4= github.com/containers/image/v5 v5.29.0/go.mod h1:kQ7qcDsps424ZAz24thD+x7+dJw1vgur3A9tTDsj97E= +github.com/containers/image/v5 v5.32.2 h1:SzNE2Y6sf9b1GJoC8qjCuMBXwQrACFp4p0RK15+4gmQ= +github.com/containers/image/v5 v5.32.2/go.mod h1:v1l73VeMugfj/QtKI+jhYbwnwFCFnNGckvbST3rQ5Hk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.1.8 h1:saSBF0/8DyPUjzcxMVzL2OBUWCkvRvqIm75pu0ADSZk= github.com/containers/ocicrypt v1.1.8/go.mod h1:jM362hyBtbwLMWzXQZTlkjKGAQf/BN/LFMtH0FIRt34= github.com/containers/ocicrypt v1.1.9 h1:2Csfba4jse85Raxk5HIyEk8OwZNjRvfkhEGijOjIdEM= github.com/containers/ocicrypt v1.1.9/go.mod h1:dTKx1918d8TDkxXvarscpNVY+lyPakPNFN4jwA9GBys= +github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM= +github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= github.com/containers/storage v1.50.2 h1:Fys4BjFUVNRBEXlO70hFI48VW4EXsgnGisTpk9tTMsE= github.com/containers/storage v1.50.2/go.mod h1:dpspZsUrcKD8SpTofvKWhwPDHD0MkO4Q7VE+oYdWkiA= github.com/containers/storage v1.51.0 h1:AowbcpiWXzAjHosKz7MKvPEqpyX+ryZA/ZurytRrFNA= github.com/containers/storage v1.51.0/go.mod h1:ybl8a3j1PPtpyaEi/5A6TOFs+5TrEyObeKJzVtkUlfc= +github.com/containers/storage v1.55.0 h1:wTWZ3YpcQf1F+dSP4KxG9iqDfpQY1otaUXjPpffuhgg= +github.com/containers/storage v1.55.0/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbFOeLunhER1RQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -233,6 +266,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= @@ -240,6 +274,8 @@ github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= 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= @@ -262,6 +298,8 @@ github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWT github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v25.0.5+incompatible h1:3Llw3kcE1gOScEojA247iDD+p1l9hHeC7H3vf3Zd5fk= github.com/docker/cli v25.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.2.0+incompatible h1:yHD1QEB1/0vr5eBNpu8tncu8gWxg8EydFPOSKHzXSMM= +github.com/docker/cli v27.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -272,12 +310,16 @@ github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= 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/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -314,10 +356,14 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +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/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -328,8 +374,12 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.1.0 h1:6j4mUV/ES2duvnAzKMFkN6/A5mCaNYPD3xfbAkLLOF8= github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -345,6 +395,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -361,11 +413,15 @@ github.com/go-git/go-billy/v5 v5.1.0 h1:4pl5BV4o7ZG/lterP4S6WzJ6xr49Ba5ET9ygheTY github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 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= @@ -390,6 +446,8 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= @@ -401,6 +459,8 @@ github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +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.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -410,6 +470,8 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +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/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= @@ -421,7 +483,10 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +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-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8= github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= @@ -429,6 +494,8 @@ github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= @@ -446,6 +513,8 @@ github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0 github.com/golang-migrate/migrate/v4 v4.16.1/go.mod h1:qXiwa/3Zeqaltm1MxOCZDYysW/F6folYiBgBG03l9hc= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -497,6 +566,8 @@ github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo= github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= +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 v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -525,6 +596,8 @@ github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYd github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= 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/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg= +github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -591,6 +664,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +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/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= @@ -645,6 +720,8 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= @@ -670,9 +747,13 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d h1:A2/B900ip/Z20TzkLeGRNy1s6J2HmH9AmGt+dHyqb4I= github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d/go.mod h1:7HQupe4vyNxMKXmM5DFuwXHsqwMyglcYmZBtlDPIcZ8= +github.com/joelanford/ignore v0.1.1 h1:vKky5RDoPT+WbONrbQBgOn95VV/UPh4ejlyAbbzgnQk= +github.com/joelanford/ignore v0.1.1/go.mod h1:8eho/D8fwQ3rIXrLwE23AaeaGDNXqLE9QJ3zJ4LIPCw= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -704,6 +785,8 @@ github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJw github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -775,6 +858,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -813,11 +898,17 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= @@ -860,6 +951,8 @@ github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -872,6 +965,8 @@ github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/ github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= @@ -881,17 +976,23 @@ github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/operator-framework/ansible-operator-plugins v1.34.1 h1:QEY4GJSErP6r8T3mjK7YvUYXAqDzUYV29n8k/Oh7WqI= github.com/operator-framework/ansible-operator-plugins v1.34.1/go.mod h1:kmgST0OcMzBchD1XXVbujgllL6hGN5SrtbdCsL7kHSM= github.com/operator-framework/ansible-operator-plugins v1.35.0 h1:ranI6NhcnAl2sokuwWI0OYqtcT/1fPIfEfWcMFLXUBg= github.com/operator-framework/ansible-operator-plugins v1.35.0/go.mod h1:ehsR1S7COaxHD54t7/1CXuvnTkSiMxUqgJhTGVcH6Fs= +github.com/operator-framework/ansible-operator-plugins v1.37.1 h1:yOcNGJChSLBTiO8BuZxphC0z1ObPegAdPKbX6IMG194= +github.com/operator-framework/ansible-operator-plugins v1.37.1/go.mod h1:rr1ornLcBtaPN806AS/G6maIvmawM3n3dRqqJDa1Bcc= github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 h1:d/Pnr19TnmIq3zQ6ebewC+5jt5zqYbRkvYd37YZENQY= github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42/go.mod h1:l/cuwtPxkVUY7fzYgdust2m9tlmb8I4pOvbsUufRb24= github.com/operator-framework/api v0.21.0 h1:89LhqGTLskxpPR4siEaorkF9PY3KLI51S5mFxP6q1G8= github.com/operator-framework/api v0.21.0/go.mod h1:3tsDLxXChMY1KgxO5v1CZQogHNQCIMy14YXkXqA5lT4= github.com/operator-framework/api v0.23.0 h1:kHymOwcHBpBVujT49SKOCd4EVG7Odwj4wl3NbOR2LLA= github.com/operator-framework/api v0.23.0/go.mod h1:oKcFOz+Xc1UhMi2Pzcp6qsO7wjS4r+yP7EQprQBXrfM= +github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/6VpifiXI= +github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM= github.com/operator-framework/helm-operator-plugins v0.0.12-0.20230413193425-4632388adc61 h1:FPO2hS4HNIU2pzWeX2KusKxqDFeGIURRMkxRtn/i570= github.com/operator-framework/helm-operator-plugins v0.0.12-0.20230413193425-4632388adc61/go.mod h1:QpVyiSOKGbWADyNRl7LvMlRuuMGrWXJQdEYyHPQWMUg= github.com/operator-framework/helm-operator-plugins v0.1.3 h1:nwl9K1Pq0NZmanpEF/DYO00S7QO/iAmEdRIuLROrYpk= @@ -910,12 +1011,16 @@ github.com/operator-framework/operator-manifest-tools v0.2.3-0.20230525225330-52 github.com/operator-framework/operator-manifest-tools v0.2.3-0.20230525225330-523bad646f89/go.mod h1:PT1D+dEbD9TCoo62TkRP4BHYXA+h+zC0i3Ql7WZW9os= github.com/operator-framework/operator-manifest-tools v0.6.0 h1:1fUP0ki3plXM6WivlcE6m5cV8fO2ZZVPHJM93vlgWJo= github.com/operator-framework/operator-manifest-tools v0.6.0/go.mod h1:rL+U7e+hpH87/kq88mbEprZpq25lwtJofsAFhq6Y/Wc= +github.com/operator-framework/operator-manifest-tools v0.8.0 h1:2zVVPs7IHrH8wgFInjF2QHJjEz9ih0qUqusMqrd4Qgg= +github.com/operator-framework/operator-manifest-tools v0.8.0/go.mod h1:oxVwdj0c7bqFBb1/bljVfImPwThORrwSn/mFn2mR4s8= github.com/operator-framework/operator-registry v1.28.0 h1:vtmd2WgJxkx7vuuOxW4k5Le/oo0SfonSeJVMU3rKIfk= github.com/operator-framework/operator-registry v1.28.0/go.mod h1:UYw3uaZyHwHgnczLRYmUqMpgRgP2EfkqOsaR+LI+nK8= github.com/operator-framework/operator-registry v1.35.0 h1:BvytqLwhgb0QiAkEODEKXq3vc2vWiHQq0IlofvFA+OI= github.com/operator-framework/operator-registry v1.35.0/go.mod h1:foC+NO1V9JuDIOk3pjjlrPE0KVkq09m8oDVRz/a/nFA= github.com/operator-framework/operator-registry v1.39.0 h1:GiAlmA2h16sLpLjVIuURd2ANm7wYoUbssGCJbdGauYw= github.com/operator-framework/operator-registry v1.39.0/go.mod h1:PxN7myibIBIHeXTNu65tIJkCl1HuFDMU3NN6jrPHJLs= +github.com/operator-framework/operator-registry v1.47.0 h1:Imr7X/W6FmXczwpIOXfnX8d6Snr1dzwWxkMG+lLAfhg= +github.com/operator-framework/operator-registry v1.47.0/go.mod h1:CJ3KcP8uRxtC8l9caM1RsV7r7jYlKAd452tcxcgXyTQ= github.com/operator-framework/operator-sdk v1.30.0 h1:0iy7BGhG+umh4z5uwxe7yZJyMgFB1b2nOIMF5WIfQDw= github.com/operator-framework/operator-sdk v1.30.0/go.mod h1:UuuI2ltaDoKm15SLQYcaBpBupNm79mtiDqOj07p7GVw= github.com/operator-framework/operator-sdk v1.31.0 h1:jnTK3lQ8JkRE0sRV3AdTmNKBZmYZaCiEkPcm3LWGKxE= @@ -926,6 +1031,10 @@ github.com/operator-framework/operator-sdk v1.34.2 h1:chRaTC8CNxo6Q63f+mBMjP5XTU github.com/operator-framework/operator-sdk v1.34.2/go.mod h1:2zrCdmaGoh0lMz0n4g9Qk8djD5+9yRVPU82lYIHWga0= github.com/operator-framework/operator-sdk v1.36.1 h1:BHStDCO38uRU0Yu/kDtmG3K9QaM+zWf3FpaAhY6KlZ0= github.com/operator-framework/operator-sdk v1.36.1/go.mod h1:m8MAGTUwjHxTTYt+zkuoKdBP2zdqfolZygtr29bWnWI= +github.com/operator-framework/operator-sdk v1.39.0 h1:N1zVqTcFGD1FWo+T3rkLLVYLZ7cr8dpntEox7VcHDd8= +github.com/operator-framework/operator-sdk v1.39.0/go.mod h1:jY9MwzTJwEfh52hucxYL5nI+2ct4bNsQQIOhr8ZIBEg= +github.com/operator-framework/operator-sdk v1.39.1 h1:e4XH07k6trz4oIqOajINfaaD1jo/64gfpKyAsnLc1gM= +github.com/operator-framework/operator-sdk v1.39.1/go.mod h1:jY9MwzTJwEfh52hucxYL5nI+2ct4bNsQQIOhr8ZIBEg= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= @@ -943,6 +1052,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -975,6 +1086,8 @@ github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+ github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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= @@ -1001,6 +1114,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1014,12 +1129,16 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 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= @@ -1030,6 +1149,8 @@ github.com/rubenv/sql-migrate v1.3.1 h1:Vx+n4Du8X8VTYuXbhNxdEUoh6wiJERA0GlWocR5F github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHurg+23VEzcsk= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= +github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1047,6 +1168,8 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -1079,6 +1202,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -1088,6 +1213,8 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1101,6 +1228,8 @@ github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= @@ -1110,6 +1239,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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= @@ -1125,6 +1255,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -1133,6 +1265,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtse github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thoas/go-funk v0.8.0 h1:JP9tKSvnpFVclYgDM0Is7FD9M4fhPvqA0s0BsXmzSRQ= github.com/thoas/go-funk v0.8.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= +github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -1149,6 +1283,8 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= @@ -1181,6 +1317,8 @@ go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= @@ -1202,12 +1340,16 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ= @@ -1216,30 +1358,40 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravY go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1247,6 +1399,8 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20221010140840-6bf6f0955179 h1:Mc5MkF55Iasgq23vSYpL6/l7EJXtlNjzw+8hbMQ/ShY= go.starlark.net v0.0.0-20221010140840-6bf6f0955179/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= @@ -1286,6 +1440,8 @@ golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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= @@ -1300,6 +1456,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 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= @@ -1334,6 +1492,8 @@ golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1399,6 +1559,8 @@ golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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= @@ -1424,6 +1586,8 @@ golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.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= @@ -1442,6 +1606,8 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1554,6 +1720,8 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1565,6 +1733,8 @@ golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 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= @@ -1581,6 +1751,8 @@ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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= @@ -1666,6 +1838,8 @@ golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 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= @@ -1786,14 +1960,19 @@ google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMK google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= google.golang.org/genproto v0.0.0-20240221002015-b0ce06bbee7c h1:Zmyn5CV/jxzKnF+3d+xzbomACPwLQqVpLTpyXN5uTaQ= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9 h1:4++qSzdWBUy9/2x8L5KZgwZw+mjJZ2yDSCGMVM0YzRs= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= +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-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1830,6 +2009,8 @@ google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1850,6 +2031,8 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1858,6 +2041,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1894,6 +2079,8 @@ helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= helm.sh/helm/v3 v3.14.3 h1:HmvRJlwyyt9HjgmAuxHbHv3PhMz9ir/XNWHyXfmnOP4= helm.sh/helm/v3 v3.14.3/go.mod h1:v6myVbyseSBJTzhmeE39UcPLNv6cQK6qss3dvgAySaE= +helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= +helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= 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= @@ -1907,36 +2094,48 @@ k8s.io/api v0.28.5 h1:XIPNr3nBgTEaCdEiwZ+dXaO9SB4NeTOZ2pNDRrFgfb4= k8s.io/api v0.28.5/go.mod h1:98zkTCc60iSnqqCIyCB1GI7PYDiRDYTSfL0PRIxpM4c= k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= +k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM= +k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw= k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= k8s.io/apiextensions-apiserver v0.28.5 h1:YKW9O9T/0Gkyl6LTFDLIhCbouSRh+pHt2vMLB38Snfc= k8s.io/apiextensions-apiserver v0.28.5/go.mod h1:7p7TQ0X9zCJLNFlOTi5dncAi2dkPsdsrcvu5ILa7PEk= k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc= +k8s.io/apiextensions-apiserver v0.31.4 h1:FxbqzSvy92Ca9DIs5jqot883G0Ln/PGXfm/07t39LS0= +k8s.io/apiextensions-apiserver v0.31.4/go.mod h1:hIW9YU8UsqZqIWGG99/gsdIU0Ar45Qd3A12QOe/rvpg= k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apimachinery v0.28.5 h1:EEj2q1qdTcv2p5wl88KavAn3VlFRjREgRu8Sm/EuMPY= k8s.io/apimachinery v0.28.5/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM= +k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= k8s.io/apiserver v0.28.5 h1:3hRmQvqkWPCQr6kYi9lrMQF84V8/ScNx/8VyjhbPTi4= k8s.io/apiserver v0.28.5/go.mod h1:tLFNbfELieGsn/utLLdSarJ99MjguBe11jkKITe3z4w= k8s.io/apiserver v0.29.3 h1:xR7ELlJ/BZSr2n4CnD3lfA4gzFivh0wwfNfz9L0WZcE= k8s.io/apiserver v0.29.3/go.mod h1:hrvXlwfRulbMbBgmWRQlFru2b/JySDpmzvQwwk4GUOs= +k8s.io/apiserver v0.31.4 h1:JbtnTaXVYEAYIHJil6Wd74Wif9sd8jVcBw84kwEmp7o= +k8s.io/apiserver v0.31.4/go.mod h1:JJjoTjZ9PTMLdIFq7mmcJy2B9xLN3HeAUebW6xZyIP0= k8s.io/cli-runtime v0.26.2 h1:6XcIQOYW1RGNwFgRwejvyUyAojhToPmJLGr0JBMC5jw= k8s.io/cli-runtime v0.26.2/go.mod h1:U7sIXX7n6ZB+MmYQsyJratzPeJwgITqrSlpr1a5wM5I= k8s.io/cli-runtime v0.28.5 h1:xTL2Zpx//2+mKysdDUogpY0qwYf5Qkuij3Ikmr6xh5Q= k8s.io/cli-runtime v0.28.5/go.mod h1:FZZy7DAfum2co5rjGMM86sumPojroT3V06mP45erB/0= k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= +k8s.io/cli-runtime v0.31.4 h1:iczCWiyXaotW+hyF5cWP8RnEYBCzZfJUF6otJ2m9mw0= +k8s.io/cli-runtime v0.31.4/go.mod h1:0/pRzAH7qc0hWx40ut1R4jLqiy2w/KnbqdaAI2eFG8U= k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/client-go v0.28.5 h1:6UNmc33vuJhh3+SAOEKku3QnKa+DtPKGnhO2MR0IEbk= k8s.io/client-go v0.28.5/go.mod h1:+pt086yx1i0HAlHzM9S+RZQDqdlzuXFl4hY01uhpcpA= k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= +k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ= +k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= @@ -1944,6 +2143,8 @@ k8s.io/component-base v0.28.5 h1:uFCW7USa8Fpme8dVtn2ZrdVaUPBRDwYJ+kNrV9OO1Cc= k8s.io/component-base v0.28.5/go.mod h1:gw2d8O28okS9RrsPuJnD2mFl2It0HH9neHiGi2xoXcY= k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= +k8s.io/component-base v0.31.4 h1:wCquJh4ul9O8nNBSB8N/o8+gbfu3BVQkVw9jAUY/Qtw= +k8s.io/component-base v0.31.4/go.mod h1:G4dgtf5BccwiDT9DdejK0qM6zTK0jwDGEKnCmb9+u/s= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1956,6 +2157,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= @@ -1963,18 +2166,24 @@ k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5Ohx k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1 h1:rtdnaWfP40MTKv7izH81gkWpZB45pZrwIxyZdPSn1mI= k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.26.2 h1:SMPB4j48eVFxsYluBq3VLyqXtE6b72YnszkbTAtFye4= k8s.io/kubectl v0.26.2/go.mod h1:KYWOXSwp2BrDn3kPeoU/uKzKtdqvhK1dgZGd0+no4cM= k8s.io/kubectl v0.28.5 h1:jq8xtiCCZPR8Cl/Qe1D7bLU0h8KtcunwfROqIekCUeU= k8s.io/kubectl v0.28.5/go.mod h1:9WiwzqeKs3vLiDtEQPbjhqqysX+BIVMLt7C7gN+T5w8= k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= +k8s.io/kubectl v0.31.4 h1:c8Af8xd1VjyoKyWMW0xHv2+tYxEjne8s6OOziMmaD10= +k8s.io/kubectl v0.31.4/go.mod h1:0E0rpXg40Q57wRE6LB9su+4tmwx1IzZrmIEvhQPk0i4= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20230711102312-30195339c3c7 h1:ZgnF1KZsYxWIifwSNZFZgNtWE89WI5yiP5WwlfDoIyc= k8s.io/utils v0.0.0-20230711102312-30195339c3c7/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.2 h1:0E9tOHUfrNH7TCDk5KU0jVBEzCqbfdyuVfGmJ7ZeRPE= oras.land/oras-go v1.2.2/go.mod h1:Apa81sKoZPpP7CDciE006tSZ0x3Q3+dOoBcMZ/aNxvw= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= @@ -1990,18 +2199,24 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6U sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/controller-runtime v0.17.4 h1:AMf1E0+93/jLQ13fb76S6Atwqp24EQFCmNbG84GJxew= sigs.k8s.io/controller-runtime v0.17.4/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= +sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= +sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= sigs.k8s.io/controller-tools v0.11.3 h1:T1xzLkog9saiyQSLz1XOImu4OcbdXWytc5cmYsBeBiE= sigs.k8s.io/controller-tools v0.11.3/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8= sigs.k8s.io/controller-tools v0.12.1 h1:GyQqxzH5wksa4n3YDIJdJJOopztR5VDM+7qsyg5yE4U= sigs.k8s.io/controller-tools v0.12.1/go.mod h1:rXlpTfFHZMpZA8aGq9ejArgZiieHd+fkk/fTatY8A2M= sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A= sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc= +sigs.k8s.io/controller-tools v0.16.5 h1:5k9FNRqziBPwqr17AMEPPV/En39ZBplLAdOwwQHruP4= +sigs.k8s.io/controller-tools v0.16.5/go.mod h1:8vztuRVzs8IuuJqKqbXCSlXcw+lkAv/M2sTpg55qjMY= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= @@ -2012,14 +2227,20 @@ sigs.k8s.io/kubebuilder/v3 v3.13.1-0.20240119130530-7fba82c768f8 h1:6dc/YGQd4QVj sigs.k8s.io/kubebuilder/v3 v3.13.1-0.20240119130530-7fba82c768f8/go.mod h1:ZhWtqslcUPr6eN/4Ww2Qn0OwxLuTt+HYLJRq/UTtJpw= sigs.k8s.io/kubebuilder/v3 v3.14.2 h1:LMZW8Y5eItnP4kh9tpp4Gs2Gd5V3DgLgzbNnXfMAShY= sigs.k8s.io/kubebuilder/v3 v3.14.2/go.mod h1:gEZM8SUkewOQnpRDiewh4gmbQ1FMkT/CDlMddOg053M= +sigs.k8s.io/kubebuilder/v4 v4.2.0 h1:vl5WgaYKR6e6YDK02Mizf7d1RxFNk1pOSnh6uRnHm6s= +sigs.k8s.io/kubebuilder/v4 v4.2.0/go.mod h1:Jq0Qrlrtn3YKdCFSW6CBbmGuwsw6xO6a7beFiVQf/bI= sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= diff --git a/.bingo/opm.mod b/.bingo/opm.mod index 9c04ecec3a..e15aae4ee6 100644 --- a/.bingo/opm.mod +++ b/.bingo/opm.mod @@ -1,7 +1,9 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.22.5 +go 1.23.0 + +toolchain go1.23.4 replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d -require github.com/operator-framework/operator-registry v1.46.0 // cmd/opm +require github.com/operator-framework/operator-registry v1.51.0 // cmd/opm diff --git a/.bingo/opm.sum b/.bingo/opm.sum index 5461aa7d9d..b98bd4c784 100644 --- a/.bingo/opm.sum +++ b/.bingo/opm.sum @@ -1,4 +1,8 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= +cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 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= @@ -55,6 +59,8 @@ github.com/Microsoft/hcsshim v0.8.25 h1:fRMwXiwk3qDwc0P05eHnh+y2v07JdtsfQ1fuAc69 github.com/Microsoft/hcsshim v0.8.25/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= +github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= +github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= @@ -73,6 +79,8 @@ github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrG github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= @@ -98,6 +106,8 @@ github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -129,15 +139,27 @@ github.com/containerd/containerd v1.5.18 h1:doHr6cNxfOLTotWmZs6aZF6LrfJFcjmYFcWl github.com/containerd/containerd v1.5.18/go.mod h1:7IN9MtIzTZH4WPEmD1gNH8bbTQXVX68yd3ZXxSHYCis= github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= +github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ= +github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= +github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= +github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -151,16 +173,34 @@ github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQ github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containers/common v0.60.1 h1:hMJNKfDxfXY91zD7mr4t/Ybe8JbAsTq5nkrUaCqTKsA= github.com/containers/common v0.60.1/go.mod h1:tB0DRxznmHviECVHnqgWbl+8AVCSMZLA8qe7+U7KD6k= +github.com/containers/common v0.61.0 h1:j/84PTqZIKKYy42OEJsZmjZ4g4Kq2ERuC3tqp2yWdh4= +github.com/containers/common v0.61.0/go.mod h1:NGRISq2vTFPSbhNqj6MLwyes4tWSlCnqbJg7R77B8xc= +github.com/containers/common v0.62.0 h1:Sl9WE5h7Y/F3bejrMAA4teP1EcY9ygqJmW4iwSloZ10= +github.com/containers/common v0.62.0/go.mod h1:Yec+z8mrSq4rydHofrnDCBqAcNA/BGrSg1kfFUL6F6s= github.com/containers/image/v5 v5.32.1 h1:fVa7GxRC4BCPGsfSRs4JY12WyeY26SUYQ0NuANaCFrI= github.com/containers/image/v5 v5.32.1/go.mod h1:v1l73VeMugfj/QtKI+jhYbwnwFCFnNGckvbST3rQ5Hk= +github.com/containers/image/v5 v5.33.0 h1:6oPEFwTurf7pDTGw7TghqGs8K0+OvPtY/UyzU0B2DfE= +github.com/containers/image/v5 v5.33.0/go.mod h1:T7HpASmvnp2H1u4cyckMvCzLuYgpD18dSmabSw0AcHk= +github.com/containers/image/v5 v5.34.0 h1:HPqQaDUsox/3mC1pbOyLAIQEp0JhQqiUZ+6JiFIZLDI= +github.com/containers/image/v5 v5.34.0/go.mod h1:/WnvUSEfdqC/ahMRd4YJDBLrpYWkGl018rB77iB3FDo= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM= github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= +github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/containers/storage v1.55.0 h1:wTWZ3YpcQf1F+dSP4KxG9iqDfpQY1otaUXjPpffuhgg= github.com/containers/storage v1.55.0/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbFOeLunhER1RQ= +github.com/containers/storage v1.56.0 h1:DZ9KSkj6M2tvj/4bBoaJu3QDHRl35BwsZ4kmLJS97ZI= +github.com/containers/storage v1.56.0/go.mod h1:c6WKowcAlED/DkWGNuL9bvGYqIWCVy7isRMdCSKWNjk= +github.com/containers/storage v1.57.1 h1:hKPoFsuBcB3qTzBxa4IFpZMRzUuL5Xhv/BE44W0XHx8= +github.com/containers/storage v1.57.1/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -172,6 +212,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -189,12 +230,20 @@ github.com/docker/cli v20.10.12+incompatible h1:lZlz0uzG+GH+c0plStMUdF/qk3ppmgns github.com/docker/cli v20.10.12+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI= github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= +github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.0.0+incompatible h1:ido37VmLUqEp+5NFb9icd6BuBB+SNDgCn+5kPCr2buA= +github.com/docker/cli v28.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= +github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= @@ -246,11 +295,15 @@ github.com/go-git/go-billy/v5 v5.1.0 h1:4pl5BV4o7ZG/lterP4S6WzJ6xr49Ba5ET9ygheTY github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 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= @@ -296,6 +349,10 @@ github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0 github.com/golang-migrate/migrate/v4 v4.16.1/go.mod h1:qXiwa/3Zeqaltm1MxOCZDYysW/F6folYiBgBG03l9hc= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -336,6 +393,8 @@ github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= +github.com/google/cel-go v0.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= +github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -355,6 +414,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 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-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -391,6 +452,8 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -398,6 +461,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QG github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= @@ -426,6 +493,8 @@ github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d h1:A2/B900ip/Z20 github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d/go.mod h1:7HQupe4vyNxMKXmM5DFuwXHsqwMyglcYmZBtlDPIcZ8= github.com/joelanford/ignore v0.1.0 h1:VawbTDeg5EL+PN7W8gxVzGerfGpVo3gFdR5ZAqnkYRk= github.com/joelanford/ignore v0.1.0/go.mod h1:Vb0PQMAQXK29fmiPjDukpO8I2NTcp1y8LbhFijD1/0o= +github.com/joelanford/ignore v0.1.1 h1:vKky5RDoPT+WbONrbQBgOn95VV/UPh4ejlyAbbzgnQk= +github.com/joelanford/ignore v0.1.1/go.mod h1:8eho/D8fwQ3rIXrLwE23AaeaGDNXqLE9QJ3zJ4LIPCw= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 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= @@ -449,6 +518,8 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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= @@ -472,6 +543,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -490,6 +563,12 @@ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/capability v0.3.0 h1:kEP+y6te0gEXIaeQhIi0s7vKs/w0RPoH1qPa6jROcVg= +github.com/moby/sys/capability v0.3.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= @@ -498,6 +577,10 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -522,6 +605,8 @@ github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -538,18 +623,28 @@ github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 h1:d/Pnr github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42/go.mod h1:l/cuwtPxkVUY7fzYgdust2m9tlmb8I4pOvbsUufRb24= github.com/operator-framework/api v0.26.0 h1:YVntU2NkVl5zSLLwK5kFcH6P3oSvN9QDgTsY9mb4yUM= github.com/operator-framework/api v0.26.0/go.mod h1:3IxOwzVUeGxYlzfwKCcfCyS+q3EEhWA/4kv7UehbeyM= +github.com/operator-framework/api v0.29.0 h1:TxAR8RCO+I4FjRrY4PSMgnlmbxNWeD8pzHXp7xwHNmw= +github.com/operator-framework/api v0.29.0/go.mod h1:0whQE4mpMDd2zyHkQe+bFa3DLoRs6oGWCbu8dY/3pyc= github.com/operator-framework/operator-registry v1.28.0 h1:vtmd2WgJxkx7vuuOxW4k5Le/oo0SfonSeJVMU3rKIfk= github.com/operator-framework/operator-registry v1.28.0/go.mod h1:UYw3uaZyHwHgnczLRYmUqMpgRgP2EfkqOsaR+LI+nK8= github.com/operator-framework/operator-registry v1.46.0 h1:t10Ej4QHsHhHswsJ/MO1WAc7LW91wb1nMCrnD6+sRV0= github.com/operator-framework/operator-registry v1.46.0/go.mod h1:tZjUHP8WUphLj/0/mkyOGdBGtrBnrn5Hj/hHnmNIybs= +github.com/operator-framework/operator-registry v1.50.0 h1:kMAwsKAEDjuSx5dGchMX+CD3SMHWwOAC/xyK3LQweB4= +github.com/operator-framework/operator-registry v1.50.0/go.mod h1:713Z/XzA5jViFMGIsXmfAcpA6h5uUKqUl3fO1t4taa0= +github.com/operator-framework/operator-registry v1.51.0 h1:3T1H2W0wYvJx82x+Ue6nooFsn859ceJf1yH6MdRdjMQ= +github.com/operator-framework/operator-registry v1.51.0/go.mod h1:dJadFTSvsgpeiqhTMK7+zXrhU0LIlx4Y/aDz0efq5oQ= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -571,6 +666,10 @@ github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= +github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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= @@ -580,6 +679,8 @@ github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvq github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +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.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -592,6 +693,10 @@ github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8 github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY= +github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -636,10 +741,14 @@ github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -661,6 +770,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs 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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= @@ -686,6 +797,10 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= 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= @@ -697,37 +812,67 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqh go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -752,6 +897,10 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= 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= @@ -818,6 +967,10 @@ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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= @@ -830,6 +983,10 @@ golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.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= @@ -845,6 +1002,10 @@ golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -903,12 +1064,20 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 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= @@ -921,6 +1090,10 @@ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 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= @@ -928,6 +1101,8 @@ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/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/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= @@ -1042,10 +1217,20 @@ google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY7 google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1069,6 +1254,10 @@ google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= 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= @@ -1086,6 +1275,10 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1094,6 +1287,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -1125,28 +1320,54 @@ k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= k8s.io/apiextensions-apiserver v0.30.3 h1:oChu5li2vsZHx2IvnGP3ah8Nj3KyqG3kRSaKmijhB9U= k8s.io/apiextensions-apiserver v0.30.3/go.mod h1:uhXxYDkMAvl6CJw4lrDN4CPbONkF3+XL9cacCT44kV4= +k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= +k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= k8s.io/apiserver v0.30.3 h1:QZJndA9k2MjFqpnyYv/PH+9PE0SHhx3hBho4X0vE65g= k8s.io/apiserver v0.30.3/go.mod h1:6Oa88y1CZqnzetd2JdepO0UXzQX4ZnOekx2/PtEjrOg= +k8s.io/apiserver v0.32.0 h1:VJ89ZvQZ8p1sLeiWdRJpRD6oLozNZD2+qVSLi+ft5Qs= +k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag= +k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= +k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= +k8s.io/cli-runtime v0.32.0 h1:dP+OZqs7zHPpGQMCGAhectbHU2SNCuZtIimRKTv2T1c= +k8s.io/cli-runtime v0.32.0/go.mod h1:Mai8ht2+esoDRK5hr861KRy6z0zHsSTYttNVJXgP3YQ= k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= +k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s= k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA= +k8s.io/component-base v0.32.0 h1:d6cWHZkCiiep41ObYQS6IcgzOUQUNpywm39KVYaUqzU= +k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM= +k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= +k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= @@ -1155,14 +1376,20 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= k8s.io/kubectl v0.26.1 h1:K8A0Jjlwg8GqrxOXxAbjY5xtmXYeYjLU96cHp2WMQ7s= k8s.io/kubectl v0.26.1/go.mod h1:miYFVzldVbdIiXMrHZYmL/EDWwJKM+F0sSsdxsATFPo= k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= +k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw= +k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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= @@ -1170,18 +1397,28 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPB sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M= sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/controller-runtime v0.18.5 h1:nTHio/W+Q4aBlQMgbnC5hZb4IjIidyrizMai9P6n4Rk= sigs.k8s.io/controller-runtime v0.18.5/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= +sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= +sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc= +sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 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/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 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/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/.bingo/setup-envtest.mod b/.bingo/setup-envtest.mod index 912aaab285..0a366239fc 100644 --- a/.bingo/setup-envtest.mod +++ b/.bingo/setup-envtest.mod @@ -1,7 +1,7 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.22.0 +go 1.24.0 -toolchain go1.22.2 +toolchain go1.24.3 -require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 +require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250620151452-b9a9ca01fd37 diff --git a/.bingo/setup-envtest.sum b/.bingo/setup-envtest.sum index 075730d122..dad3e24e86 100644 --- a/.bingo/setup-envtest.sum +++ b/.bingo/setup-envtest.sum @@ -20,8 +20,12 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= 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/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -40,6 +44,8 @@ go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -63,6 +69,12 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 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/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -81,5 +93,15 @@ sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230606045100-e54088c sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230606045100-e54088c8c7da/go.mod h1:B6HLcvOy2S1qq2eWOFm9xepiKPMIc8Z9OXSPsnUDaR4= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 h1:Wzx3QswG7gfzqPDw7Ec6/xvJGyoxAKUEoaxWLrk1V/I= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250114080233-1ec7c1b76e98 h1:CfrkP9Dz+H1eLdEfsDq3hr2BujXLFPSzNlQv5snC1to= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250114080233-1ec7c1b76e98/go.mod h1:Is2SwCWbWAoyGVoVBA627n1SWhWaEwUhaIYSEbtzHT4= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250217160221-5e8256e05002 h1:vl1ohLP3ehNsJc/6X21vexTkPsH2jFHq1sPCATw15IU= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250217160221-5e8256e05002/go.mod h1:QXw4XLB4ayZHsgXTf7cdyGzacNz9KQsdiI6apU+K07E= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250226022829-9d8d219840a4 h1:/esRUCAd/0ujMip84n2e2j/lDDYe84EOLDSeQToREZw= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250226022829-9d8d219840a4/go.mod h1:QXw4XLB4ayZHsgXTf7cdyGzacNz9KQsdiI6apU+K07E= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250304084143-6eb011f4f89e h1:ezClPOTx54T3hRw/3eNMYr5LKzikTvQ380UuGy/X/Co= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250304084143-6eb011f4f89e/go.mod h1:QXw4XLB4ayZHsgXTf7cdyGzacNz9KQsdiI6apU+K07E= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250620151452-b9a9ca01fd37 h1:NSnbH7C6/fYc5L3FxMQiSlFBqYi+32LnFsXwArzOlIM= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250620151452-b9a9ca01fd37/go.mod h1:zCcqn1oG9844T8/vZSYcnqOyoEmTHro4bliTJI6j4OY= 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/.bingo/variables.env b/.bingo/variables.env index 669b17ad40..64c92fc14b 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -10,21 +10,29 @@ fi BINGO="${GOBIN}/bingo-v0.9.0" -CONTROLLER_GEN="${GOBIN}/controller-gen-v0.16.1" +CONTROLLER_GEN="${GOBIN}/controller-gen-v0.19.0" + +CRD_DIFF="${GOBIN}/crd-diff-v0.2.0" CRD_REF_DOCS="${GOBIN}/crd-ref-docs-v0.1.0" -GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.60.3" +GOJQ="${GOBIN}/gojq-v0.12.17" + +GOLANGCI_LINT="${GOBIN}/golangci-lint-v2.1.6" GORELEASER="${GOBIN}/goreleaser-v1.26.2" -KIND="${GOBIN}/kind-v0.24.0" +HELM="${GOBIN}/helm-v3.18.4" + +KIND="${GOBIN}/kind-v0.30.0" + +KUSTOMIZE="${GOBIN}/kustomize-v5.6.0" -KUSTOMIZE="${GOBIN}/kustomize-v4.5.7" +OPERATOR_SDK="${GOBIN}/operator-sdk-v1.39.1" -OPERATOR_SDK="${GOBIN}/operator-sdk-v1.36.1" +OPM="${GOBIN}/opm-v1.51.0" -OPM="${GOBIN}/opm-v1.46.0" +SETUP_ENVTEST="${GOBIN}/setup-envtest-v0.0.0-20250620151452-b9a9ca01fd37" -SETUP_ENVTEST="${GOBIN}/setup-envtest-v0.0.0-20240820183333-e6c3d139d2b6" +YAMLFMT="${GOBIN}/yamlfmt-v0.20.0" diff --git a/.bingo/yamlfmt.mod b/.bingo/yamlfmt.mod new file mode 100644 index 0000000000..152ea9ecb9 --- /dev/null +++ b/.bingo/yamlfmt.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.24.4 + +require github.com/google/yamlfmt v0.20.0 // cmd/yamlfmt diff --git a/.bingo/yamlfmt.sum b/.bingo/yamlfmt.sum new file mode 100644 index 0000000000..e9450c1919 --- /dev/null +++ b/.bingo/yamlfmt.sum @@ -0,0 +1,16 @@ +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/yamlfmt v0.20.0 h1:EfMuMFEZGnXPn2NY+KgJTH9sNs6P+am/Of6IwE2K6P8= +github.com/google/yamlfmt v0.20.0/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..83f42b89a6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore test and tool binaries. +bin/controller-gen +bin/goreleaser +bin/kustomize +bin/envtest +testbin/ +.tiltbuild/ diff --git a/.github/OWNERS b/.github/OWNERS new file mode 100644 index 0000000000..835cabe507 --- /dev/null +++ b/.github/OWNERS @@ -0,0 +1,2 @@ +approvers: + - ci-approvers diff --git a/.github/workflows/crd-diff.yaml b/.github/workflows/crd-diff.yaml new file mode 100644 index 0000000000..3bb66d293a --- /dev/null +++ b/.github/workflows/crd-diff.yaml @@ -0,0 +1,20 @@ +name: crd-diff +on: + pull_request: +jobs: + crd-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run make verify-crd-compatibility + run: | + make verify-crd-compatibility \ + CRD_DIFF_ORIGINAL_REF="git://${{ github.event.pull_request.base.sha }}?path=" \ + CRD_DIFF_UPDATED_REF="git://${{ github.event.pull_request.head.sha }}?path=" diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 06a2e9d31a..f5e1c109ff 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -12,50 +12,102 @@ jobs: extension-developer-e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Run the extension developer e2e test - run: make extension-developer-e2e + run: make test-extension-developer-e2e e2e-kind: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Run e2e tests - run: ARTIFACT_PATH=/tmp/artifacts make test-e2e + run: ARTIFACT_PATH=/tmp/artifacts E2E_SUMMARY_OUTPUT=$GITHUB_STEP_SUMMARY make test-e2e - - uses: cytopia/upload-artifact-retry-action@v0.1.7 + - uses: actions/upload-artifact@v5 if: failure() with: name: e2e-artifacts path: /tmp/artifacts/ - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5.5.1 with: disable_search: true files: coverage/e2e.out flags: e2e token: ${{ secrets.CODECOV_TOKEN }} + experimental-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run e2e tests + run: ARTIFACT_PATH=/tmp/artifacts E2E_SUMMARY_OUTPUT=$GITHUB_STEP_SUMMARY make test-experimental-e2e + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: experimental-e2e-artifacts + path: /tmp/artifacts/ + + - uses: codecov/codecov-action@v5.5.1 + with: + disable_search: true + files: coverage/experimental-e2e.out + flags: experimental-e2e + token: ${{ secrets.CODECOV_TOKEN }} + upgrade-e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Run the upgrade e2e test - run: make test-upgrade-e2e + run: ARTIFACT_PATH=/tmp/artifacts make test-upgrade-e2e + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: upgrade-e2e-artifacts + path: /tmp/artifacts/ + + upgrade-experimental-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run the upgrade e2e test + run: ARTIFACT_PATH=/tmp/artifacts make test-upgrade-experimental-e2e + + - uses: actions/upload-artifact@v5 + if: failure() + with: + name: upgrade-experimental-e2e-artifacts + path: /tmp/artifacts/ + diff --git a/.github/workflows/go-apidiff.yaml b/.github/workflows/go-apidiff.yaml index 4228e997ee..44d53621cd 100644 --- a/.github/workflows/go-apidiff.yaml +++ b/.github/workflows/go-apidiff.yaml @@ -1,16 +1,17 @@ name: go-apidiff on: + merge_group: pull_request: jobs: go-apidiff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/go-verdiff.yaml b/.github/workflows/go-verdiff.yaml new file mode 100644 index 0000000000..82b0d201cd --- /dev/null +++ b/.github/workflows/go-verdiff.yaml @@ -0,0 +1,22 @@ +name: go-verdiff +on: + pull_request: + branches: + - main +jobs: + go-verdiff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Check golang version + run: | + export LABELS="$(gh api repos/$OWNER/$REPO/pulls/$PR --jq '.labels.[].name')" + hack/tools/check-go-version.sh -b "${{ github.event.pull_request.base.sha }}" + shell: bash + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + PR: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 65b70dcf2d..b757aec9ea 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -30,9 +30,9 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.x cache: pip diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 43ceeca64b..bd472f578d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,12 +18,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: "go.mod" @@ -42,10 +42,10 @@ jobs: echo IMAGE_TAG="${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV echo GORELEASER_ARGS="--clean" >> $GITHUB_ENV echo ENABLE_RELEASE_PIPELINE=true >> $GITHUB_ENV - elif [[ $GITHUB_REF == refs/heads/* ]]; then - # Branch build. + elif [[ $GITHUB_REF == refs/heads/main ]]; then + # 'main' branch build. echo IMAGE_TAG="$(echo "${GITHUB_REF#refs/heads/}" | sed -r 's|/+|-|g')" >> $GITHUB_ENV - echo GORELEASER_ARGS="--clean --skip-validate" >> $GITHUB_ENV + echo GORELEASER_ARGS="--clean --skip=validate" >> $GITHUB_ENV elif [[ $GITHUB_REF == refs/pull/* ]]; then # PR build. echo IMAGE_TAG="pr-$(echo "${GITHUB_REF}" | sed -E 's|refs/pull/([^/]+)/?.*|\1|')" >> $GITHUB_ENV diff --git a/.github/workflows/sanity.yaml b/.github/workflows/sanity.yaml index 5b7a641a36..fde951310c 100644 --- a/.github/workflows/sanity.yaml +++ b/.github/workflows/sanity.yaml @@ -12,9 +12,9 @@ jobs: verify: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: "go.mod" - name: Run verification checks @@ -22,11 +22,23 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: "go.mod" - name: Run golangci linting checks - run: make lint GOLANGCI_LINT_ARGS="--out-format github-actions" + run: make lint + + lint-helm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + + - name: Run helm linting checks + run: make lint-helm diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..95cb101668 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,55 @@ +# This workflow automatically marks issues and pull requests as stale after 90 days of inactivity +# and closes them after an additional 30 days if no further activity occurs. +# +# Key behavior: +# - After 90 days of no activity: +# - Open issues and pull requests are labeled with "lifecycle/stale" +# - A comment is posted to notify contributors about the inactivity +# +# - After 30 additional days (i.e., 120 days total): +# - If still inactive and still labeled "lifecycle/stale", the issue or PR is closed +# - A closing comment is posted to explain why it was closed +# +# - Activity such as a comment, commit, or label removal during the stale period +# will remove the "lifecycle/stale" label and reset the clock +# +# - Items with any of the following labels will never be marked stale or closed: +# - security +# - planned +# - priority/critical +# - lifecycle/frozen +# - verified +# +# This workflow uses: https://github.com/actions/stale +name: "Close stale issues and PRs" +on: + schedule: + - cron: "0 1 * * *" # Runs daily at 01:00 UTC + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + # allow labeling, commenting, closing issues and PRs + issues: write + pull-requests: write + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 30 + stale-issue-label: "lifecycle/stale" + stale-pr-label: "lifecycle/stale" + stale-issue-message: > + Issues go stale after 90 days of inactivity. If there is no further + activity, the issue will be closed in another 30 days. + stale-pr-message: > + PRs go stale after 90 days of inactivity. If there is no further + activity, the PR will be closed in another 30 days. + close-issue-message: "This issue has been closed due to inactivity." + close-pr-message: "This pull request has been closed due to inactivity." + exempt-issue-labels: "security,planned,priority/critical,lifecycle/frozen,verified" + exempt-pr-labels: "security,planned,priority/critical,lifecycle/frozen,verified" + operations-per-run: 30 + diff --git a/.github/workflows/test-regression.yaml b/.github/workflows/test-regression.yaml new file mode 100644 index 0000000000..3b09074af2 --- /dev/null +++ b/.github/workflows/test-regression.yaml @@ -0,0 +1,31 @@ +name: test-regression + +on: + workflow_dispatch: + pull_request: + merge_group: + push: + branches: + - main + +jobs: + test-regression: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v5 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run regression tests + run: | + make test-regression + + - uses: codecov/codecov-action@v5.5.1 + with: + disable_search: true + files: coverage/regression.out + flags: unit + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tilt.yaml b/.github/workflows/tilt.yaml index e8f2d3dc04..858250a132 100644 --- a/.github/workflows/tilt.yaml +++ b/.github/workflows/tilt.yaml @@ -6,32 +6,24 @@ on: - 'api/**' - 'cmd/**' - 'config/**' + - 'catalogd/**' - 'internal/**' - 'pkg/**' - 'Tiltfile' + - '.tilt-support' merge_group: jobs: tilt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - repository: operator-framework/tilt-support - path: tilt-support - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: operator-controller - - name: Get catalogd version - id: get-catalogd-version - run: | - cd operator-controller - echo "CATALOGD_VERSION=$(go list -mod=mod -m -f "{{.Version}}" github.com/operator-framework/catalogd)" >> "$GITHUB_OUTPUT" - - uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v6 with: - repository: operator-framework/catalogd - path: catalogd - ref: "${{ steps.get-catalogd-version.outputs.CATALOGD_VERSION }}" + go-version-file: "operator-controller/go.mod" - name: Install Tilt run: | TILT_VERSION="0.33.3" diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index 07778deb4e..0579b1219d 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -23,7 +23,7 @@ jobs: run: | make test-unit - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5.5.1 with: disable_search: true files: coverage/unit.out diff --git a/.github/workflows/update-demos.yaml b/.github/workflows/update-demos.yaml new file mode 100644 index 0000000000..29d6fd4c47 --- /dev/null +++ b/.github/workflows/update-demos.yaml @@ -0,0 +1,39 @@ +name: update-demos + +on: + schedule: + - cron: '0 3 * * *' # Runs every day at 03:00 UTC + workflow_dispatch: + push: + paths: + - 'api/*' + - 'config/*' + - 'hack/demo/*' + - '.github/workflows/update-demos.yaml' + pull_request: + paths: + - 'api/*' + - 'config/*' + - 'hack/demo/*' + - '.github/workflows/update-demos.yaml' + +jobs: + demo: + runs-on: ubuntu-latest + env: + TERM: linux + steps: + - run: sudo apt update && sudo apt install -y asciinema curl + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + - name: Run Demo Update + run: | + env -i \ + HOME="$HOME" \ + PATH="$PATH" \ + TERM="xterm-256color" \ + SHELL="/bin/bash" \ + make update-demos + diff --git a/.gitignore b/.gitignore index d7ffdb1b3d..abd509dafb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -vendor - # Binaries for programs and plugins *.exe *.exe~ @@ -10,6 +8,7 @@ bin/* testbin/* /hack/tools/bin/* Dockerfile.cross +crd_work_dir/ # Test binary, build with `go test -c` *.test @@ -17,26 +16,37 @@ Dockerfile.cross # Output of the go coverage tools *.out coverage +cover.out # Release output -dist/** -operator-controller.yaml -install.sh - -# Kubernetes Generated files - skip generated files, except for vendored files +/dist/** +/operator-controller.yaml +/operator-controller-experimental.yaml +/default-catalogs.yaml +/install.sh +/install-experimental.sh -!vendor/**/zz_generated.* +# vendored files +vendor/ # editor and IDE paraphernalia -.idea +.idea/ +.run/ *.swp *.swo *~ \#*\# .\#* +# AI temp files files +.claude/ + # documentation website asset folder site .tiltbuild/ -.vscode \ No newline at end of file +.catalogd-tmp/ +.vscode + +# Temporary files and directories +/test/regression/convert/testdata/tmp/* diff --git a/.golangci.yaml b/.golangci.yaml index 6ccb7c35d7..e4ba57da9c 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,71 +1,74 @@ -######## -# NOTE -# -# This file is duplicated in the following repos: -# - operator-framework/kubectl-operator -# - operator-framework/catalogd -# - operator-framework/operator-controller -# -# If you are making a change, please make it in ALL -# of the above repositories! -# -# TODO: Find a way to have a shared golangci config. -######## - -run: - # Default timeout is 1m, up to give more room - timeout: 4m - -linters: - enable: - - asciicheck - - bodyclose - - errorlint - - gci - - gofmt - - gosec - - importas - - misspell - - nestif - - nonamedreturns - - prealloc - - stylecheck - - tparallel - - unconvert - - unparam - - unused - - whitespace - -linters-settings: - gci: - sections: - - standard - - dot - - default - - prefix(github.com/operator-framework) - - localmodule - custom-order: true - - errorlint: - errorf: false - - importas: - alias: - - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 - alias: metav1 - - pkg: k8s.io/apimachinery/pkg/api/errors - alias: apierrors - - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 - alias: apiextensionsv1 - - pkg: k8s.io/apimachinery/pkg/util/runtime - alias: utilruntime - - pkg: "^k8s\\.io/api/([^/]+)/(v[^/]+)$" - alias: $1$2 - - pkg: sigs.k8s.io/controller-runtime - alias: ctrl - - pkg: github.com/blang/semver/v4 - alias: bsemver - +version: "2" output: formats: - - format: tab + tab: + path: stdout + colors: false +linters: + enable: + - asciicheck + - bodyclose + - errorlint + - gosec + - importas + - misspell + - nestif + - nonamedreturns + - prealloc + - staticcheck + - testifylint + - tparallel + - unconvert + - unparam + - whitespace + settings: + errorlint: + errorf: false + importas: + alias: + - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 + alias: metav1 + - pkg: k8s.io/apimachinery/pkg/api/errors + alias: apierrors + - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 + alias: apiextensionsv1 + - pkg: k8s.io/apimachinery/pkg/util/runtime + alias: utilruntime + - pkg: ^k8s\.io/api/([^/]+)/(v[^/]+)$ + alias: $1$2 + - pkg: sigs.k8s.io/controller-runtime + alias: ctrl + - pkg: github.com/blang/semver/v4 + alias: bsemver + - pkg: ^github.com/operator-framework/operator-controller/internal/util/([^/]+)$ + alias: ${1}util + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + settings: + gci: + sections: + - standard + - dot + - default + - prefix(github.com/operator-framework) + - localmodule + custom-order: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml index 19358457a5..7200142142 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -4,11 +4,30 @@ before: - go mod download builds: - id: operator-controller - main: ./cmd/manager/ - binary: manager + main: ./cmd/operator-controller/ + binary: operator-controller asmflags: "{{ .Env.GO_BUILD_ASMFLAGS }}" gcflags: "{{ .Env.GO_BUILD_GCFLAGS }}" ldflags: "{{ .Env.GO_BUILD_LDFLAGS }}" + tags: + - "{{ .Env.GO_BUILD_TAGS }}" + mod_timestamp: "{{ .CommitTimestamp }}" + goos: + - linux + goarch: + - amd64 + - arm64 + - ppc64le + - s390x + - id: catalogd + main: ./cmd/catalogd/ + binary: catalogd + asmflags: "{{ .Env.GO_BUILD_ASMFLAGS }}" + gcflags: "{{ .Env.GO_BUILD_GCFLAGS }}" + ldflags: "{{ .Env.GO_BUILD_LDFLAGS }}" + tags: + - "{{ .Env.GO_BUILD_TAGS }}" + mod_timestamp: "{{ .CommitTimestamp }}" goos: - linux goarch: @@ -18,44 +37,82 @@ builds: - s390x dockers: - image_templates: - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" - dockerfile: Dockerfile + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + dockerfile: Dockerfile.operator-controller goos: linux goarch: amd64 use: buildx build_flag_templates: - "--platform=linux/amd64" - image_templates: - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" - dockerfile: Dockerfile + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + dockerfile: Dockerfile.operator-controller goos: linux goarch: arm64 use: buildx build_flag_templates: - "--platform=linux/arm64" - image_templates: - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" - dockerfile: Dockerfile + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + dockerfile: Dockerfile.operator-controller goos: linux goarch: ppc64le use: buildx build_flag_templates: - "--platform=linux/ppc64le" - image_templates: - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" - dockerfile: Dockerfile + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + dockerfile: Dockerfile.operator-controller + goos: linux + goarch: s390x + use: buildx + build_flag_templates: + - "--platform=linux/s390x" + - image_templates: + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + dockerfile: Dockerfile.catalogd + goos: linux + goarch: amd64 + use: buildx + build_flag_templates: + - "--platform=linux/amd64" + - image_templates: + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + dockerfile: Dockerfile.catalogd + goos: linux + goarch: arm64 + use: buildx + build_flag_templates: + - "--platform=linux/arm64" + - image_templates: + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + dockerfile: Dockerfile.catalogd + goos: linux + goarch: ppc64le + use: buildx + build_flag_templates: + - "--platform=linux/ppc64le" + - image_templates: + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + dockerfile: Dockerfile.catalogd goos: linux goarch: s390x use: buildx build_flag_templates: - "--platform=linux/s390x" docker_manifests: - - name_template: "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}" + - name_template: "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}" + image_templates: + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.OPCON_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + - name_template: "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}" image_templates: - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" - - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.CATD_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" checksum: name_template: 'checksums.txt' snapshot: @@ -65,12 +122,16 @@ changelog: disable: '{{ ne .Env.ENABLE_RELEASE_PIPELINE "true" }}' release: disable: '{{ ne .Env.ENABLE_RELEASE_PIPELINE "true" }}' + mode: replace extra_files: - - glob: 'operator-controller.yaml' - - glob: 'install.sh' + - glob: '{{ .Env.STANDARD_RELEASE_MANIFEST }}' + - glob: '{{ .Env.STANDARD_RELEASE_INSTALL }}' + - glob: '{{ .Env.EXPERIMENTAL_RELEASE_MANIFEST }}' + - glob: '{{ .Env.EXPERIMENTAL_RELEASE_INSTALL }}' + - glob: '{{ .Env.RELEASE_CATALOGS }}' header: | ## Installation ```bash curl -L -s https://github.com/operator-framework/operator-controller/releases/download/{{ .Tag }}/install.sh | bash -s - ``` \ No newline at end of file + ``` diff --git a/.tilt-support b/.tilt-support new file mode 100644 index 0000000000..9cb01b1526 --- /dev/null +++ b/.tilt-support @@ -0,0 +1,153 @@ +load('ext://restart_process', 'docker_build_with_restart') +load('ext://cert_manager', 'deploy_cert_manager') + + +def deploy_cert_manager_if_needed(): + cert_manager_var = '__CERT_MANAGER__' + if os.getenv(cert_manager_var) != '1': + deploy_cert_manager(version="v1.18.2") + os.putenv(cert_manager_var, '1') + + +# Set up our build helper image that has delve in it. We use a helper so parallel image builds don't all simultaneously +# install delve. Instead, they all wait for this build to complete, and then proceed in parallel. +docker_build( + ref='helper', + context='.', + build_args={'GO_VERSION': '1.24'}, + dockerfile_contents=''' +ARG GO_VERSION +FROM golang:${GO_VERSION} +ARG GO_VERSION +RUN CGO_ENABLED=0 go install github.com/go-delve/delve/cmd/dlv@v${GO_VERSION} +''' +) + + +def build_binary(repo, binary, deps, image, tags="", debug=True): + gcflags = '' + if debug: + gcflags = "-gcflags 'all=-N -l'" + + # Treat the main binary as a local resource, so we can automatically rebuild it when any of the deps change. This + # builds it locally, targeting linux, so it can run in a linux container. + binary_name = binary.split("/")[-1] + local_resource( + '{}_{}_binary'.format(repo, binary_name), + cmd=''' +mkdir -p .tiltbuild/bin +CGO_ENABLED=0 GOOS=linux go build {tags} {gcflags} -o .tiltbuild/bin/{binary_name} {binary} +'''.format(repo=repo, binary_name=binary_name, binary=binary, gcflags=gcflags, tags=tags), + deps=deps + ) + + entrypoint = ['/{}'.format(binary_name)] + if debug: + entrypoint = ['/dlv', '--accept-multiclient', '--api-version=2', '--headless=true', '--listen', ':30000', 'exec', '--continue', '--'] + entrypoint + + # Configure our image build. If the file in live_update.sync (.tiltbuild/bin/$binary) changes, Tilt + # copies it to the running container and restarts it. + docker_build_with_restart( + # This has to match an image in the k8s_yaml we call below, so Tilt knows to use this image for our Deployment, + # instead of the actual image specified in the yaml. + ref='{image}:{binary_name}'.format(image=image, binary_name=binary_name), + # This is the `docker build` context, and because we're only copying in the binary we've already had Tilt build + # locally, we set the context to the directory containing the binary. + context='.tiltbuild/bin', + # We use a slimmed-down Dockerfile that only has $binary in it. + dockerfile_contents=''' +FROM gcr.io/distroless/static:debug +WORKDIR / +COPY --from=helper /go/bin/dlv / +COPY {} / + '''.format(binary_name), + # The set of files Tilt should include in the build. In this case, it's just the binary we built above. + only=binary_name, + # If .tiltbuild/bin/$binary changes, Tilt will copy it into the running container and restart the process. + live_update=[ + sync('.tiltbuild/bin/{}'.format(binary_name), '/{}'.format(binary_name)), + ], + restart_file="/.tilt_restart_proc", + # The command to run in the container. + entrypoint=entrypoint, + ) + + +def process_yaml(yaml): + if type(yaml) == 'string': + objects = read_yaml_stream(yaml) + elif type(yaml) == 'blob': + objects = decode_yaml_stream(yaml) + else: + fail('expected a string or blob, got: {}'.format(type(yaml))) + + for o in objects: + # For Tilt's live_update functionality to work, we have to run the container as root. Remove any PSA labels + # to allow this. + if o['kind'] == 'Namespace' and 'labels' in o['metadata']: + labels_to_delete = [label for label in o['metadata']['labels'] if label.startswith('pod-security.kubernetes.io')] + for label in labels_to_delete: + o['metadata']['labels'].pop(label) + + if o['kind'] != 'Deployment': + # We only need to modify Deployments, so we can skip this + continue + + # For Tilt's live_update functionality to work, we have to run the container as root. Otherwise, Tilt won't + # be able to untar the updated binary in the container's file system (this is how live update + # works). If there are any securityContexts, remove them. + if "securityContext" in o['spec']['template']['spec']: + o['spec']['template']['spec'].pop('securityContext') + for c in o['spec']['template']['spec']['containers']: + if "securityContext" in c: + c.pop('securityContext') + + # If multiple Deployment manifests all use the same image but use different entrypoints to change the binary, + # we have to adjust each Deployment to use a different image. Tilt needs each Deployment's image to be + # unique. We replace the tag with what is effectively :$binary, e.g. :helm. + for c in o['spec']['template']['spec']['containers']: + if c['name'] == 'kube-rbac-proxy': + continue + + command = c['command'][0] + if command.startswith('./'): + command = command.removeprefix('./') + elif command.startswith('/'): + command = command.removeprefix('/') + + image_without_tag = c['image'].rsplit(':', 1)[0] + + # Update the image so instead of :$tag it's :$binary + c['image'] = '{}:{}'.format(image_without_tag, command) + + # Now apply all the yaml + # We are using allow_duplicates=True here as both + # operator-controller and catalogd will be installed in the same + # namespace "olmv1-system" as of https://github.com/operator-framework/operator-controller/pull/888 + # and https://github.com/operator-framework/catalogd/pull/283 + k8s_yaml(encode_yaml_stream(objects), allow_duplicates=True) + + +# data format: +# { +# 'repos': { +# 'catalogd': { +# 'image': 'quay.io/operator-framework/catalogd', +# 'binary': './cmd/catalogd', +# 'deployment': 'catalogd-controller-manager', +# 'deps': ['api', 'cmd/catalogd', 'internal/catalogd', 'internal/shared', 'go.mod', 'go.sum'], +# 'starting_debug_port': 20000, +# }, +# ... additional entries here ... +# }, +# 'yaml': 'config/overlays/tilt-local-dev', +# } + +def deploy_repo(data, tags="", debug=True): + deploy_cert_manager_if_needed() + for reponame, repo in data['repos'].items(): + print('Deploying repo {}'.format(reponame)) + local_port = repo['starting_debug_port'] + build_binary(reponame, repo['binary'], repo['deps'], repo['image'], tags, debug) + k8s_resource(repo['deployment'], port_forwards=['{}:30000'.format(local_port)]) + process_yaml(helm('helm/olmv1', name="olmv1", values=[data['yaml']])) diff --git a/.tiltignore b/.tiltignore new file mode 100644 index 0000000000..29976f9b83 --- /dev/null +++ b/.tiltignore @@ -0,0 +1,10 @@ +.md +.txt +.toml +LICENSE +Makefile +.github +.vscode +*_test.go +*/test +.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78a858d256..156ae32e62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,19 +7,18 @@ Operator Controller is an Apache 2.0 licensed project and accepts contributions By contributing to this project you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the -contribution. See the [DCO](DCO) file for details. +contribution. See the [DCO](https://github.com/operator-framework/operator-controller/blob/main/DCO) file for details. ## Overview Thank you for your interest in contributing to the Operator-Controller. -As you may or may not know, the Operator-Controller project aims to deliver the user experience described in the [Operator Lifecycle Manager (OLM) V1 Product Requirements Document (PRD)](https://docs.google.com/document/d/1-vsZ2dAODNfoHb7Nf0fbYeKDF7DUqEzS9HqgeMCvbDs/edit). The design requirements captured in the OLM V1 PRD were born from customer and community feedback based on the experience they had with the released version of [OLM V0](github.com/operator-framework/operator-lifecycle-manager). +As you may or may not know, the Operator-Controller project aims to deliver the user experience described in the [Operator Lifecycle Manager (OLM) V1 Product Requirements Document (PRD)](https://docs.google.com/document/d/1-vsZ2dAODNfoHb7Nf0fbYeKDF7DUqEzS9HqgeMCvbDs/edit). The design requirements captured in the OLM V1 PRD were born from customer and community feedback based on the experience they had with the released version of [OLM V0](https://github.com/operator-framework/operator-lifecycle-manager). -The user experience captured in the OLM V1 PRD introduces many requirements that are best satisfied by a microservices architecture. The OLM V1 experience currently relies on two projects: -- [The Operator-Controller project](https://github.com/operator-framework/operator-controller/), which is the top level component allowing users to specify operators they'd like to install. -- [The Catalogd project](https://github.com/operator-framework/catalogd/), which hosts operator content and helps users discover installable content. +The user experience captured in the OLM V1 PRD introduces many requirements that are best satisfied by a microservices architecture. The OLM V1 experience currently relies on two components: -Each of the projects listed above have their own governance, release milestones, and release cadence. However, from a technical perspective, the "OLM V1 experience" matches the experienced offered by the operator-controller project, the top level component which depends on Catalogd. +- [Operator-Controller](https://github.com/operator-framework/operator-controller/), which is the top level component allowing users to specify operators they'd like to install. +- [Catalogd](https://github.com/operator-framework/operator-controller/tree/main/catalogd), which hosts operator content and helps users discover installable content. ## How do we collaborate @@ -35,16 +34,66 @@ The workflow defined above implies that the community is always ready for discus Please keep this workflow in mind as you read through the document. +## How to Build and Deploy Locally + +After creating a fork and cloning the project locally, +you can follow the steps below to test your changes: + +1. Create the cluster: + + ```sh + kind create cluster -n operator-controller + ``` + +2. Build your changes: + + ```sh + make build docker-build + ``` + +3. Load the image locally and Deploy to Kind + + ```sh + make kind-load kind-deploy + ``` + +## How to debug controller tests using ENVTEST + +[ENVTEST](https://book.kubebuilder.io/reference/envtest) requires k8s binaries to be downloaded to run the tests. +To download the necessary binaries, follow the steps below: + +```sh +make envtest-k8s-bins +``` + +Note that the binaries are downloaded to the `bin/envtest-binaries` directory. + +```sh +$ tree +. +├── envtest-binaries +│   └── k8s +│   └── 1.31.0-darwin-arm64 +│   ├── etcd +│   ├── kube-apiserver +│   └── kubectl +``` + +Now, you can debug them with your IDE: + +![Screenshot IDE example](https://github.com/user-attachments/assets/3096d524-0686-48ca-911c-5b843093ad1f) + ### Communication Channels - Email: [operator-framework-olm-dev](mailto:operator-framework-olm-dev@googlegroups.com) - Slack: [#olm-dev](https://kubernetes.slack.com/archives/C0181L6JYQ2) - Google Group: [olm-gg](https://groups.google.com/g/operator-framework-olm-dev) -- Weekly in Person Working Group Meeting: [olm-wg](https://github.com/operator-framework/community#operator-lifecycle-manager-working-group) +- Weekly in Person Working Group Meeting: [olm-wg](https://github.com/operator-framework/community#operator-lifecycle-manager-working-group) ## How are Milestones Designed? It's unreasonable to attempt to consider all of the design requirements laid out in the [OLM V1 PRD](https://docs.google.com/document/d/1-vsZ2dAODNfoHb7Nf0fbYeKDF7DUqEzS9HqgeMCvbDs/edit) from the onset of the project. Instead, the community attempts to design Milestones with the following principles: + - Milestones are tightly scoped units of work, ideally lasting one to three weeks. - Milestones are derived from the OLM V1 PRD. - Milestones are "demo driven", meaning that a set of acceptance criteria is defined upfront and the milestone is done as soon as some member of the community can run the demo. @@ -52,7 +101,7 @@ It's unreasonable to attempt to consider all of the design requirements laid out This "demo driven" development model will allow us to collect user experience and regularly course correct based on user feedback. Subsequent milestone may revert features or change the user experience based on community feedback. -The project maintainer will create a [GitHub Discussion](github.com/operator-framework/operator-controller/discussions) for the upcoming milestone once we've finalized the current milestone. Please feel encouraged to contribute suggestions for the milestone in the discussion. +The project maintainer will create a [GitHub Discussion](https://github.com/operator-framework/operator-controller/discussions) for the upcoming milestone once we've finalized the current milestone. Please feel encouraged to contribute suggestions for the milestone in the discussion. ## Where are Operator Controller Milestones? @@ -66,11 +115,9 @@ As discussed earlier, the operator-controller adheres to a microservice architec ## Submitting Issues -Unsure where to submit an issue? -- [The Operator-Controller project](https://github.com/operator-framework/operator-controller/), which is the top level component allowing users to specify operators they'd like to install. -- [The Catalogd project](https://github.com/operator-framework/catalogd/), which hosts operator content and helps users discover installable content. +Unsure where to submit an issue? -Don't worry if you accidentally submit an issue against the wrong project, if we notice that an issue would fit better with a separate project we'll move it to the correct repository and mention it in the #olm-dev slack channel. +- [Operator-Controller](https://github.com/operator-framework/operator-controller/), which contains both components, is the project allowing users to specify operators they'd like to install. ## Submitting Pull Requests @@ -87,7 +134,7 @@ approach of changes. When contributing changes that require a new dependency, check whether it's feasible to directly vendor that code [without introducing a new dependency](https://go-proverbs.github.io/). -Currently, PRs require at least one approval from a operator-controller maintainer in order to get merged. +Currently, PRs require at least one approval from an operator-controller maintainer in order to get merged. ### Code style @@ -102,6 +149,14 @@ focusing less on style conflicts, and more on the design and implementation deta Please follow this style to make the operator-controller project easier to review, maintain and develop. +### Go version + +Our goal is to minimize disruption by requiring the lowest possible Go language version. This means avoiding updaties to the go version specified in the project's `go.mod` file (and other locations). + +There is a GitHub PR CI job named `go-verdiff` that will inform a PR author if the Go language version has been updated. It is not a required test, but failures should prompt authors and reviewers to have a discussion with the community about the Go language version change. + +There may be ways to avoid a Go language version change by using not-the-most-recent versions of dependencies. We do acknowledge that CVE fixes might require a specific dependency version that may have updated to a newer version of the Go language. + ### Documentation If the contribution changes the existing APIs or user interface it must include sufficient documentation to explain the diff --git a/Dockerfile.catalogd b/Dockerfile.catalogd new file mode 100644 index 0000000000..d0833c1fe3 --- /dev/null +++ b/Dockerfile.catalogd @@ -0,0 +1,8 @@ +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY catalogd catalogd +USER 65532:65532 + +ENTRYPOINT ["/catalogd"] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile.operator-controller similarity index 73% rename from Dockerfile rename to Dockerfile.operator-controller index 03c737e3f7..0fe53b71e0 100644 --- a/Dockerfile +++ b/Dockerfile.operator-controller @@ -2,11 +2,9 @@ # required and is intended to be built only with the # 'make build' or 'make release' targets. FROM gcr.io/distroless/static:nonroot - WORKDIR / - -COPY manager manager - +COPY operator-controller operator-controller EXPOSE 8080 - USER 65532:65532 + +ENTRYPOINT ["/operator-controller"] \ No newline at end of file diff --git a/Makefile b/Makefile index f36df3f915..cf7b1d6508 100644 --- a/Makefile +++ b/Makefile @@ -7,31 +7,50 @@ SHELL := /usr/bin/env bash -o pipefail .SHELLFLAGS := -ec export ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +GOLANG_VERSION := $(shell sed -En 's/^go (.*)$$/\1/p' "go.mod") # Image URL to use all building/pushing image targets -ifeq ($(origin IMAGE_REPO), undefined) -IMAGE_REPO := quay.io/operator-framework/operator-controller +ifeq ($(origin IMAGE_REGISTRY), undefined) +IMAGE_REGISTRY := quay.io/operator-framework endif -export IMAGE_REPO +export IMAGE_REGISTRY + +ifeq ($(origin OPCON_IMAGE_REPO), undefined) +OPCON_IMAGE_REPO := $(IMAGE_REGISTRY)/operator-controller +endif +export OPCON_IMAGE_REPO + +ifeq ($(origin CATD_IMAGE_REPO), undefined) +CATD_IMAGE_REPO := $(IMAGE_REGISTRY)/catalogd +endif +export CATD_IMAGE_REPO ifeq ($(origin IMAGE_TAG), undefined) IMAGE_TAG := devel endif export IMAGE_TAG -IMG := $(IMAGE_REPO):$(IMAGE_TAG) +OPCON_IMG := $(OPCON_IMAGE_REPO):$(IMAGE_TAG) +CATD_IMG := $(CATD_IMAGE_REPO):$(IMAGE_TAG) + +# Extract Kubernetes client-go version used to set the version to the PSA labels, for ENVTEST and KIND +ifeq ($(origin K8S_VERSION), undefined) +K8S_VERSION := $(shell go list -m k8s.io/client-go | cut -d" " -f2 | sed -E 's/^v0\.([0-9]+)\.[0-9]+$$/1.\1/') +endif + +# Ensure ENVTEST_VERSION follows correct "X.Y.x" format +ENVTEST_VERSION := $(K8S_VERSION).x # Define dependency versions (use go.mod if we also use Go code from dependency) -export CERT_MGR_VERSION := v1.15.3 -export CATALOGD_VERSION := $(shell go list -mod=mod -m -f "{{.Version}}" github.com/operator-framework/catalogd) +export CERT_MGR_VERSION := v1.18.2 export WAIT_TIMEOUT := 60s # Install default ClusterCatalogs export INSTALL_DEFAULT_CATALOGS := true -# By default setup-envtest will write to $XDG_DATA_HOME, or $HOME/.local/share if that is not defined. +# By default setup-envtest binary will write to $XDG_DATA_HOME, or $HOME/.local/share if that is not defined. # If $HOME is not set, we need to specify a binary directory to prevent an error in setup-envtest. # Useful for some CI/CD environments that set neither $XDG_DATA_HOME nor $HOME. -SETUP_ENVTEST_BIN_DIR_OVERRIDE= +SETUP_ENVTEST_BIN_DIR_OVERRIDE += --bin-dir $(ROOT_DIR)/bin/envtest-binaries ifeq ($(shell [[ $$HOME == "" || $$HOME == "/" ]] && [[ $$XDG_DATA_HOME == "" ]] && echo true ), true) SETUP_ENVTEST_BIN_DIR_OVERRIDE += --bin-dir /tmp/envtest-binaries endif @@ -43,12 +62,6 @@ ifeq ($(origin KIND_CLUSTER_NAME), undefined) KIND_CLUSTER_NAME := operator-controller endif -# Not guaranteed to have patch releases available and node image tags are full versions (i.e v1.28.0 - no v1.28, v1.29, etc.) -# The KIND_NODE_VERSION is set by getting the version of the k8s.io/client-go dependency from the go.mod -# and sets major version to "1" and the patch version to "0". For example, a client-go version of v0.28.5 -# will map to a KIND_NODE_VERSION of 1.28.0 -KIND_NODE_VERSION := $(shell go list -m k8s.io/client-go | cut -d" " -f2 | sed 's/^v0\.\([[:digit:]]\{1,\}\)\.[[:digit:]]\{1,\}$$/1.\1.0/') -KIND_CLUSTER_IMAGE := kindest/node:v$(KIND_NODE_VERSION) ifneq (, $(shell command -v docker 2>/dev/null)) CONTAINER_RUNTIME := docker @@ -58,7 +71,19 @@ else $(warning Could not find docker or podman in path! This may result in targets requiring a container runtime failing!) endif -KUSTOMIZE_BUILD_DIR := config/overlays/cert-manager +export STANDARD_RELEASE_MANIFEST := operator-controller.yaml +export STANDARD_RELEASE_INSTALL := install.sh +export EXPERIMENTAL_RELEASE_MANIFEST := operator-controller-experimental.yaml +export EXPERIMENTAL_RELEASE_INSTALL := install-experimental.sh +export RELEASE_CATALOGS := default-catalogs.yaml + +# List of manifests that are checked in +MANIFEST_HOME := manifests +STANDARD_MANIFEST := $(MANIFEST_HOME)/standard.yaml +STANDARD_E2E_MANIFEST := $(MANIFEST_HOME)/standard-e2e.yaml +EXPERIMENTAL_MANIFEST := $(MANIFEST_HOME)/experimental.yaml +EXPERIMENTAL_E2E_MANIFEST := $(MANIFEST_HOME)/experimental-e2e.yaml +CATALOGS_MANIFEST := $(MANIFEST_HOME)/default-catalogs.yaml # Disable -j flag for make .NOTPARALLEL: @@ -81,7 +106,7 @@ KUSTOMIZE_BUILD_DIR := config/overlays/cert-manager .PHONY: help help: #HELP Display essential help. - @awk 'BEGIN {FS = ":[^#]*#HELP"; printf "\nUsage:\n make \033[36m\033[0m\n\n"} /^[a-zA-Z_0-9-]+:.*#HELP / { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } ' $(MAKEFILE_LIST) + @awk 'BEGIN {FS = ":[^#]*#HELP"; printf "\nUsage:\n make \033[36m\033[0m\n\n"} /^[a-zA-Z_0-9-]+:.*#HELP / { printf " \033[36m%-21s\033[0m %s\n", $$1, $$2 } ' $(MAKEFILE_LIST) .PHONY: help-extended help-extended: #HELP Display extended help. @@ -90,46 +115,99 @@ help-extended: #HELP Display extended help. #SECTION Development .PHONY: lint -lint: $(GOLANGCI_LINT) #HELP Run golangci linter. - $(GOLANGCI_LINT) run $(GOLANGCI_LINT_ARGS) +lint: lint-custom $(GOLANGCI_LINT) #HELP Run golangci linter. + $(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS) + +lint-helm: $(HELM) #HELP Run helm linter + helm lint helm/olmv1 + helm lint helm/prometheus -.PHONY: tidy -tidy: #HELP Update dependencies. - $(Q)go mod tidy +.PHONY: custom-linter-build +custom-linter-build: #EXHELP Build custom linter + go build -tags $(GO_BUILD_TAGS) -o ./bin/custom-linter ./hack/ci/custom-linters/cmd +.PHONY: lint-custom +lint-custom: custom-linter-build #EXHELP Call custom linter for the project + go vet -tags=$(GO_BUILD_TAGS) -vettool=./bin/custom-linter ./... + +.PHONY: k8s-pin +k8s-pin: #EXHELP Pin k8s staging modules based on k8s.io/kubernetes version (in go.mod or from K8S_IO_K8S_VERSION env var) and run go mod tidy. + K8S_IO_K8S_VERSION='$(K8S_IO_K8S_VERSION)' go run hack/tools/k8smaintainer/main.go + +.PHONY: tidy #HELP Run go mod tidy. +tidy: + go mod tidy + +# Due to https://github.com/kubernetes-sigs/controller-tools/issues/837 we can't specify individual files +# So we have to generate them together and then move them into place +.PHONY: update-crds +update-crds: + hack/tools/update-crds.sh + +# The filename variables can be overridden on the command line if you want to change the set of values files: +# e.g. make "manifests/standard.yaml=helm/cert-manager.yaml my-values-file.yaml" manifests +# +# The set of MANIFESTS to be generated can be changed; you can generate your own custom manifest +# e.g. make MANIFESTS=test.yaml "test.yaml=helm/e2e.yaml" manifests +# +# Override HELM_SETTINGS on the command line to include additional Helm settings +# e.g. make HELM_SETTINGS="options.openshift.enabled=true" manifests +# e.g. make HELM_SETTINGS="options.operatorController.features.enabled={WebhookProviderCertManager}" manifests +# +MANIFESTS ?= $(STANDARD_MANIFEST) $(STANDARD_E2E_MANIFEST) $(EXPERIMENTAL_MANIFEST) $(EXPERIMENTAL_E2E_MANIFEST) +$(STANDARD_MANIFEST) ?= helm/cert-manager.yaml +$(STANDARD_E2E_MANIFEST) ?= helm/cert-manager.yaml helm/e2e.yaml +$(EXPERIMENTAL_MANIFEST) ?= helm/cert-manager.yaml helm/experimental.yaml +$(EXPERIMENTAL_E2E_MANIFEST) ?= helm/cert-manager.yaml helm/experimental.yaml helm/e2e.yaml +HELM_SETTINGS ?= +.PHONY: $(MANIFESTS) +$(MANIFESTS): $(HELM) + @mkdir -p $(MANIFEST_HOME) + $(HELM) template olmv1 helm/olmv1 $(addprefix --values ,$($@)) $(addprefix --set ,$(HELM_SETTINGS)) > $@ + +# Generate manifests stored in source-control .PHONY: manifests -manifests: $(CONTROLLER_GEN) #EXHELP Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases output:rbac:artifacts:config=config/base/rbac +manifests: update-crds $(MANIFESTS) $(HELM) #EXHELP Generate OLMv1 manifests + # These are testing existing manifest options without saving the results + $(HELM) template olmv1 helm/olmv1 --values helm/tilt.yaml $(addprefix --set ,$(HELM_SETTINGS)) > /dev/null + $(HELM) template olmv1 helm/olmv1 --set "options.openshift.enabled=true" > /dev/null .PHONY: generate generate: $(CONTROLLER_GEN) #EXHELP Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + @find api cmd hack internal -name "zz_generated.deepcopy.go" -delete # Need to delete the files for them to be generated properly + $(CONTROLLER_GEN) --load-build-tags=$(GO_BUILD_TAGS) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: verify -verify: tidy fmt vet generate manifests crd-ref-docs #HELP Verify all generated code is up-to-date. +verify: k8s-pin kind-verify-versions fmt generate manifests update-tls-profiles crd-ref-docs #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy. git diff --exit-code .PHONY: fix-lint fix-lint: $(GOLANGCI_LINT) #EXHELP Fix lint issues - $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_ARGS) + $(GOLANGCI_LINT) run --fix --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS) .PHONY: fmt -fmt: #EXHELP Formats code +fmt: $(YAMLFMT) #EXHELP Formats code go fmt ./... + $(YAMLFMT) -gitignore_excludes testdata -.PHONY: vet -vet: #EXHELP Run go vet against code. - go vet ./... +.PHONY: update-tls-profiles +update-tls-profiles: $(GOJQ) #EXHELP Update TLS profiles from the Mozilla wiki + env JQ=$(GOJQ) hack/tools/update-tls-profiles.sh -.PHONY: bingo-upgrade -bingo-upgrade: $(BINGO) #EXHELP Upgrade tools - @for pkg in $$($(BINGO) list | awk '{ print $$1 }' | tail -n +3); do \ - echo "Upgrading $$pkg to latest..."; \ - $(BINGO) get "$$pkg@latest"; \ - done +.PHONY: verify-crd-compatibility +CRD_DIFF_ORIGINAL_REF := git://main?path= +CRD_DIFF_UPDATED_REF := file:// +CRD_DIFF_OPCON_SOURCE := helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml +CRD_DIFF_CATD_SOURCE := helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml +CRD_DIFF_CONFIG := crd-diff-config.yaml +verify-crd-compatibility: $(CRD_DIFF) manifests + $(CRD_DIFF) --config="${CRD_DIFF_CONFIG}" "${CRD_DIFF_ORIGINAL_REF}${CRD_DIFF_OPCON_SOURCE}" ${CRD_DIFF_UPDATED_REF}${CRD_DIFF_OPCON_SOURCE} + $(CRD_DIFF) --config="${CRD_DIFF_CONFIG}" "${CRD_DIFF_ORIGINAL_REF}${CRD_DIFF_CATD_SOURCE}" ${CRD_DIFF_UPDATED_REF}${CRD_DIFF_CATD_SOURCE} + +#SECTION Test .PHONY: test -test: manifests generate fmt vet test-unit test-e2e #HELP Run all tests. +test: manifests generate fmt lint test-unit test-e2e test-regression #HELP Run all tests. .PHONY: e2e e2e: #EXHELP Run the e2e tests. @@ -139,30 +217,50 @@ E2E_REGISTRY_NAME := docker-registry E2E_REGISTRY_NAMESPACE := operator-controller-e2e export REG_PKG_NAME := registry-operator -export LOCAL_REGISTRY_HOST := $(E2E_REGISTRY_NAME).$(E2E_REGISTRY_NAMESPACE).svc:5000 -export CLUSTER_REGISTRY_HOST := localhost:30000 +export CLUSTER_REGISTRY_HOST := $(E2E_REGISTRY_NAME).$(E2E_REGISTRY_NAMESPACE).svc:5000 +export LOCAL_REGISTRY_HOST := localhost:30000 export E2E_TEST_CATALOG_V1 := e2e/test-catalog:v1 export E2E_TEST_CATALOG_V2 := e2e/test-catalog:v2 -export CATALOG_IMG := $(LOCAL_REGISTRY_HOST)/$(E2E_TEST_CATALOG_V1) -.PHONY: test-ext-dev-e2e -test-ext-dev-e2e: $(OPERATOR_SDK) $(KUSTOMIZE) $(KIND) #HELP Run extension create, upgrade and delete tests. - test/extension-developer-e2e/setup.sh $(OPERATOR_SDK) $(CONTAINER_RUNTIME) $(KUSTOMIZE) $(KIND) $(KIND_CLUSTER_NAME) $(E2E_REGISTRY_NAMESPACE) +export CATALOG_IMG := $(CLUSTER_REGISTRY_HOST)/$(E2E_TEST_CATALOG_V1) +.PHONY: extension-developer-e2e +extension-developer-e2e: $(OPERATOR_SDK) $(KUSTOMIZE) #EXHELP Run extension create, upgrade and delete tests. + test/extension-developer-e2e/setup.sh $(OPERATOR_SDK) $(CONTAINER_RUNTIME) $(KUSTOMIZE) ${LOCAL_REGISTRY_HOST} ${CLUSTER_REGISTRY_HOST} go test -count=1 -v ./test/extension-developer-e2e/... -.PHONY: test-unit -ENVTEST_VERSION := $(shell go list -m k8s.io/client-go | cut -d" " -f2 | sed 's/^v0\.\([[:digit:]]\{1,\}\)\.[[:digit:]]\{1,\}$$/1.\1.x/') -UNIT_TEST_DIRS := $(shell go list ./... | grep -v /test/) +UNIT_TEST_DIRS := $(shell go list ./... | grep -vE "/test/|/testutils") COVERAGE_UNIT_DIR := $(ROOT_DIR)/coverage/unit -test-unit: $(SETUP_ENVTEST) #HELP Run the unit tests - rm -rf $(COVERAGE_UNIT_DIR) && mkdir -p $(COVERAGE_UNIT_DIR) - eval $$($(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION) $(SETUP_ENVTEST_BIN_DIR_OVERRIDE)) && CGO_ENABLED=1 go test -count=1 -race -short $(UNIT_TEST_DIRS) -cover -coverprofile ${ROOT_DIR}/coverage/unit.out -test.gocoverdir=$(ROOT_DIR)/coverage/unit -image-registry: ## Setup in-cluster image registry - ./hack/test/image-registry.sh $(E2E_REGISTRY_NAMESPACE) $(E2E_REGISTRY_NAME) +.PHONY: envtest-k8s-bins #HELP Uses setup-envtest to download and install the binaries required to run ENVTEST-test based locally at the project/bin directory. +envtest-k8s-bins: $(SETUP_ENVTEST) + mkdir -p $(ROOT_DIR)/bin + $(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION) $(SETUP_ENVTEST_BIN_DIR_OVERRIDE) -build-push-e2e-catalog: ## Build the testdata catalog used for e2e tests and push it to the image registry - ./hack/test/build-push-e2e-catalog.sh $(E2E_REGISTRY_NAMESPACE) $(LOCAL_REGISTRY_HOST)/$(E2E_TEST_CATALOG_V1) - ./hack/test/build-push-e2e-catalog.sh $(E2E_REGISTRY_NAMESPACE) $(LOCAL_REGISTRY_HOST)/$(E2E_TEST_CATALOG_V2) +.PHONY: test-unit +test-unit: $(SETUP_ENVTEST) envtest-k8s-bins #HELP Run the unit tests + rm -rf $(COVERAGE_UNIT_DIR) && mkdir -p $(COVERAGE_UNIT_DIR) + KUBEBUILDER_ASSETS="$(shell $(SETUP_ENVTEST) use -p path $(ENVTEST_VERSION) $(SETUP_ENVTEST_BIN_DIR_OVERRIDE))" \ + CGO_ENABLED=1 go test \ + -tags '$(GO_BUILD_TAGS)' \ + -cover -coverprofile ${ROOT_DIR}/coverage/unit.out \ + -count=1 -race -short \ + $(UNIT_TEST_DIRS) \ + -test.gocoverdir=$(COVERAGE_UNIT_DIR) + +COVERAGE_REGRESSION_DIR := $(ROOT_DIR)/coverage/regression +.PHONY: test-regression +test-regression: #HELP Run regression test + rm -rf $(COVERAGE_REGRESSION_DIR) && mkdir -p $(COVERAGE_REGRESSION_DIR) + go test -count=1 -v ./test/regression/... -cover -coverprofile ${ROOT_DIR}/coverage/regression.out -test.gocoverdir=$(COVERAGE_REGRESSION_DIR) + +.PHONY: image-registry +E2E_REGISTRY_IMAGE=localhost/e2e-test-registry:devel +image-registry: export GOOS=linux +image-registry: export GOARCH=amd64 +image-registry: ## Build the testdata catalog used for e2e tests and push it to the image registry + go build $(GO_BUILD_FLAGS) $(GO_BUILD_EXTRA_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o ./testdata/push/bin/push ./testdata/push/push.go + $(CONTAINER_RUNTIME) build -f ./testdata/Dockerfile -t $(E2E_REGISTRY_IMAGE) ./testdata + $(CONTAINER_RUNTIME) save $(E2E_REGISTRY_IMAGE) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) + ./testdata/build-test-registry.sh $(E2E_REGISTRY_NAMESPACE) $(E2E_REGISTRY_NAME) $(E2E_REGISTRY_IMAGE) # When running the e2e suite, you can set the ARTIFACT_PATH variable to the absolute path # of the directory for the operator-controller e2e tests to store the artifacts, which @@ -170,20 +268,37 @@ build-push-e2e-catalog: ## Build the testdata catalog used for e2e tests and pus # # for example: ARTIFACT_PATH=/tmp/artifacts make test-e2e .PHONY: test-e2e +test-e2e: SOURCE_MANIFEST := $(STANDARD_E2E_MANIFEST) test-e2e: KIND_CLUSTER_NAME := operator-controller-e2e -test-e2e: KUSTOMIZE_BUILD_DIR := config/overlays/e2e -test-e2e: GO_BUILD_FLAGS := -cover -test-e2e: run image-registry build-push-e2e-catalog registry-load-bundles e2e e2e-coverage kind-clean #HELP Run e2e test suite on local kind cluster - -.PHONY: extension-developer-e2e -extension-developer-e2e: KUSTOMIZE_BUILD_DIR := config/overlays/cert-manager -extension-developer-e2e: KIND_CLUSTER_NAME := operator-controller-ext-dev-e2e #EXHELP Run extension-developer e2e on local kind cluster -extension-developer-e2e: export INSTALL_DEFAULT_CATALOGS := false #EXHELP Run extension-developer e2e on local kind cluster -extension-developer-e2e: run image-registry test-ext-dev-e2e kind-clean +test-e2e: GO_BUILD_EXTRA_FLAGS := -cover +test-e2e: COVERAGE_NAME := e2e +test-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +test-e2e: run-internal image-registry prometheus e2e e2e-coverage kind-clean #HELP Run e2e test suite on local kind cluster + +.PHONY: test-experimental-e2e +test-experimental-e2e: SOURCE_MANIFEST := $(EXPERIMENTAL_E2E_MANIFEST) +test-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-e2e +test-experimental-e2e: GO_BUILD_EXTRA_FLAGS := -cover +test-experimental-e2e: COVERAGE_NAME := experimental-e2e +test-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +test-experimental-e2e: run-internal image-registry prometheus e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster + +.PHONY: prometheus +prometheus: PROMETHEUS_NAMESPACE := olmv1-system +prometheus: PROMETHEUS_VERSION := v0.83.0 +prometheus: $(KUSTOMIZE) #EXHELP Deploy Prometheus into specified namespace + ./hack/test/install-prometheus.sh $(PROMETHEUS_NAMESPACE) $(PROMETHEUS_VERSION) $(VERSION) + +.PHONY: test-extension-developer-e2e +test-extension-developer-e2e: SOURCE_MANIFEST := $(STANDARD_E2E_MANIFEST) +test-extension-developer-e2e: KIND_CLUSTER_NAME := operator-controller-ext-dev-e2e +test-extension-developer-e2e: export INSTALL_DEFAULT_CATALOGS := false +test-extension-developer-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +test-extension-developer-e2e: run-internal image-registry extension-developer-e2e kind-clean #HELP Run extension-developer e2e on local kind cluster .PHONY: run-latest-release run-latest-release: - curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s + curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/$(notdir $(RELEASE_INSTALL)) | bash -s .PHONY: pre-upgrade-setup pre-upgrade-setup: @@ -193,48 +308,73 @@ pre-upgrade-setup: post-upgrade-checks: go test -count=1 -v ./test/upgrade-e2e/... + +TEST_UPGRADE_E2E_TASKS := kind-cluster run-latest-release image-registry pre-upgrade-setup docker-build kind-load kind-deploy post-upgrade-checks kind-clean + .PHONY: test-upgrade-e2e +test-upgrade-e2e: SOURCE_MANIFEST := $(STANDARD_MANIFEST) +test-upgrade-e2e: RELEASE_INSTALL := $(STANDARD_RELEASE_INSTALL) test-upgrade-e2e: KIND_CLUSTER_NAME := operator-controller-upgrade-e2e +test-upgrade-e2e: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) test-upgrade-e2e: export TEST_CLUSTER_CATALOG_NAME := test-catalog test-upgrade-e2e: export TEST_CLUSTER_EXTENSION_NAME := test-package -test-upgrade-e2e: kind-cluster run-latest-release image-registry build-push-e2e-catalog registry-load-bundles pre-upgrade-setup docker-build kind-load kind-deploy post-upgrade-checks kind-clean #HELP Run upgrade e2e tests on a local kind cluster +test-upgrade-e2e: $(TEST_UPGRADE_E2E_TASKS) #HELP Run upgrade e2e tests on a local kind cluster + +.PHONY: test-upgrade-experimental-e2e +test-upgrade-experimental-e2e: SOURCE_MANIFEST := $(EXPERIMENTAL_MANIFEST) +test-upgrade-experimental-e2e: RELEASE_INSTALL := $(EXPERIMENTAL_RELEASE_INSTALL) +test-upgrade-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-upgrade-experimental-e2e +test-upgrade-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +test-upgrade-experimental-e2e: export TEST_CLUSTER_CATALOG_NAME := test-catalog +test-upgrade-experimental-e2e: export TEST_CLUSTER_EXTENSION_NAME := test-package +test-upgrade-experimental-e2e: $(TEST_UPGRADE_E2E_TASKS) #HELP Run upgrade e2e tests on a local kind cluster + .PHONY: e2e-coverage e2e-coverage: - COVERAGE_OUTPUT=./coverage/e2e.out ./hack/test/e2e-coverage.sh + COVERAGE_NAME=$(COVERAGE_NAME) ./hack/test/e2e-coverage.sh + +#SECTION KIND Cluster Operations .PHONY: kind-load -kind-load: $(KIND) #EXHELP Loads the currently constructed image onto the cluster. - $(CONTAINER_RUNTIME) save $(IMG) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) +kind-load: $(KIND) #EXHELP Loads the currently constructed images into the KIND cluster. + $(CONTAINER_RUNTIME) save $(OPCON_IMG) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) + $(CONTAINER_RUNTIME) save $(CATD_IMG) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) .PHONY: kind-deploy -kind-deploy: export MANIFEST="./operator-controller.yaml" -kind-deploy: manifests $(KUSTOMIZE) #EXHELP Install controller and dependencies onto the kind cluster. - $(KUSTOMIZE) build $(KUSTOMIZE_BUILD_DIR) > operator-controller.yaml - envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$INSTALL_DEFAULT_CATALOGS,$$MANIFEST' < scripts/install.tpl.sh | bash -s +kind-deploy: export DEFAULT_CATALOG := $(RELEASE_CATALOGS) +kind-deploy: manifests + @echo -e "\n\U1F4D8 Using $(SOURCE_MANIFEST) as source manifest\n" + sed "s/cert-git-version/cert-$(VERSION)/g" $(SOURCE_MANIFEST) > $(MANIFEST) + cp $(CATALOGS_MANIFEST) $(DEFAULT_CATALOG) + envsubst '$$DEFAULT_CATALOG,$$CERT_MGR_VERSION,$$INSTALL_DEFAULT_CATALOGS,$$MANIFEST' < scripts/install.tpl.sh | bash -s .PHONY: kind-cluster -kind-cluster: $(KIND) #EXHELP Standup a kind cluster. +kind-cluster: $(KIND) kind-verify-versions #EXHELP Standup a kind cluster. -$(KIND) delete cluster --name $(KIND_CLUSTER_NAME) - $(KIND) create cluster --name $(KIND_CLUSTER_NAME) --image $(KIND_CLUSTER_IMAGE) --config ./kind-config.yaml + $(KIND) create cluster --name $(KIND_CLUSTER_NAME) --config ./kind-config.yaml $(KIND) export kubeconfig --name $(KIND_CLUSTER_NAME) .PHONY: kind-clean kind-clean: $(KIND) #EXHELP Delete the kind cluster. $(KIND) delete cluster --name $(KIND_CLUSTER_NAME) -registry-load-bundles: ## Load selected e2e testdata container images created in kind-load-bundles into registry - testdata/bundles/registry-v1/build-push-e2e-bundle.sh ${E2E_REGISTRY_NAMESPACE} $(LOCAL_REGISTRY_HOST)/bundles/registry-v1/prometheus-operator:v1.0.0 prometheus-operator.v1.0.0 prometheus-operator.v1.0.0 - testdata/bundles/registry-v1/build-push-e2e-bundle.sh ${E2E_REGISTRY_NAMESPACE} $(LOCAL_REGISTRY_HOST)/bundles/registry-v1/prometheus-operator:v1.0.1 prometheus-operator.v1.0.1 prometheus-operator.v1.0.0 - testdata/bundles/registry-v1/build-push-e2e-bundle.sh ${E2E_REGISTRY_NAMESPACE} $(LOCAL_REGISTRY_HOST)/bundles/registry-v1/prometheus-operator:v1.2.0 prometheus-operator.v1.2.0 prometheus-operator.v1.0.0 - testdata/bundles/registry-v1/build-push-e2e-bundle.sh ${E2E_REGISTRY_NAMESPACE} $(LOCAL_REGISTRY_HOST)/bundles/registry-v1/prometheus-operator:v2.0.0 prometheus-operator.v2.0.0 prometheus-operator.v1.0.0 +.PHONY: kind-verify-versions +kind-verify-versions: + env K8S_VERSION=v$(K8S_VERSION) KIND=$(KIND) GOBIN=$(GOBIN) hack/tools/validate_kindest_node.sh + #SECTION Build -ifeq ($(origin VERSION), undefined) +# attempt to generate the VERSION attribute for certificates +# fail if it is unset afterwards, since the side effects are indirect +ifeq ($(strip $(VERSION)),) VERSION := $(shell git describe --tags --always --dirty) endif export VERSION +ifeq ($(strip $(VERSION)),) + $(error undefined VERSION; resulting certs will be invalid) +endif ifeq ($(origin CGO_ENABLED), undefined) CGO_ENABLED := 0 @@ -242,20 +382,23 @@ endif export CGO_ENABLED export GIT_REPO := $(shell go list -m) -export VERSION_PATH := ${GIT_REPO}/internal/version +export VERSION_PATH := ${GIT_REPO}/internal/shared/version +export GO_BUILD_TAGS := containers_image_openpgp export GO_BUILD_ASMFLAGS := all=-trimpath=$(PWD) export GO_BUILD_GCFLAGS := all=-trimpath=$(PWD) -export GO_BUILD_FLAGS := +export GO_BUILD_EXTRA_FLAGS := export GO_BUILD_LDFLAGS := -s -w \ -X '$(VERSION_PATH).version=$(VERSION)' \ + -X '$(VERSION_PATH).gitCommit=$(GIT_COMMIT)' \ -BINARIES=manager +BINARIES=operator-controller catalogd +.PHONY: $(BINARIES) $(BINARIES): - go build $(GO_BUILD_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/$@ ./cmd/$@ + go build $(GO_BUILD_FLAGS) $(GO_BUILD_EXTRA_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/$@ ./cmd/$@ .PHONY: build-deps -build-deps: manifests generate fmt vet +build-deps: manifests generate fmt .PHONY: build go-build-local build: build-deps go-build-local #HELP Build manager binary for current GOOS and GOARCH. Default target. @@ -269,12 +412,28 @@ go-build-linux: export GOOS=linux go-build-linux: export GOARCH=amd64 go-build-linux: $(BINARIES) +.PHONY: run-internal +run-internal: docker-build kind-cluster kind-load kind-deploy wait + .PHONY: run -run: docker-build kind-cluster kind-load kind-deploy #HELP Build the operator-controller then deploy it into a new kind cluster. +run: SOURCE_MANIFEST := $(STANDARD_MANIFEST) +run: export MANIFEST := $(STANDARD_RELEASE_MANIFEST) +run: run-internal #HELP Build operator-controller then deploy it with the standard manifest into a new kind cluster. + +.PHONY: run-experimental +run-experimental: SOURCE_MANIFEST := $(EXPERIMENTAL_MANIFEST) +run-experimental: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST) +run-experimental: run-internal #HELP Build the operator-controller then deploy it with the experimental manifest into a new kind cluster. + +CATD_NAMESPACE := olmv1-system +wait: + kubectl wait --for=condition=Available --namespace=$(CATD_NAMESPACE) deployment/catalogd-controller-manager --timeout=60s + kubectl wait --for=condition=Ready --namespace=$(CATD_NAMESPACE) certificate/catalogd-service-cert # Avoid upgrade test flakes when reissuing cert .PHONY: docker-build -docker-build: build-linux #EXHELP Build docker image for operator-controller with GOOS=linux and local GOARCH. - $(CONTAINER_RUNTIME) build -t $(IMG) -f Dockerfile ./bin/linux +docker-build: build-linux #EXHELP Build docker image for operator-controller and catalog with GOOS=linux and local GOARCH. + $(CONTAINER_RUNTIME) build -t $(OPCON_IMG) -f Dockerfile.operator-controller ./bin/linux + $(CONTAINER_RUNTIME) build -t $(CATD_IMG) -f Dockerfile.catalogd ./bin/linux #SECTION Release ifeq ($(origin ENABLE_RELEASE_PIPELINE), undefined) @@ -289,31 +448,37 @@ export GORELEASER_ARGS .PHONY: release release: $(GORELEASER) #EXHELP Runs goreleaser for the operator-controller. By default, this will run only as a snapshot and will not publish any artifacts unless it is run with different arguments. To override the arguments, run with "GORELEASER_ARGS=...". When run as a github action from a tag, this target will publish a full release. - $(GORELEASER) $(GORELEASER_ARGS) + OPCON_IMAGE_REPO=$(OPCON_IMAGE_REPO) CATD_IMAGE_REPO=$(CATD_IMAGE_REPO) $(GORELEASER) $(GORELEASER_ARGS) .PHONY: quickstart -quickstart: export MANIFEST := https://github.com/operator-framework/operator-controller/releases/download/$(VERSION)/operator-controller.yaml -quickstart: $(KUSTOMIZE) manifests #EXHELP Generate the installation release manifests and scripts. - $(KUSTOMIZE) build $(KUSTOMIZE_BUILD_DIR) | sed "s/:devel/:$(VERSION)/g" > operator-controller.yaml - envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$INSTALL_DEFAULT_CATALOGS,$$MANIFEST' < scripts/install.tpl.sh > install.sh +quickstart: export STANDARD_MANIFEST_URL := "https://github.com/operator-framework/operator-controller/releases/download/$(VERSION)/$(notdir%20$(STANDARD_RELEASE_MANIFEST))" +quickstart: export EXPERIMENTAL_MANIFEST_URL := "https://github.com/operator-framework/operator-controller/releases/download/$(VERSION)/$(notdir%20$(EXPERIMENTAL_RELEASE_MANIFEST))" +quickstart: export DEFAULT_CATALOG := "https://github.com/operator-framework/operator-controller/releases/download/$(VERSION)/$(notdir%20$(RELEASE_CATALOGS))" +quickstart: manifests #EXHELP Generate the unified installation release manifests and scripts. + # Update the stored standard manifests for distribution + sed "s/:devel/:$(VERSION)/g" $(STANDARD_MANIFEST) | sed "s/cert-git-version/cert-$(VERSION)/g" > $(STANDARD_RELEASE_MANIFEST) + sed "s/:devel/:$(VERSION)/g" $(EXPERIMENTAL_MANIFEST) | sed "s/cert-git-version/cert-$(VERSION)/g" > $(EXPERIMENTAL_RELEASE_MANIFEST) + cp $(CATALOGS_MANIFEST) $(RELEASE_CATALOGS) + MANIFEST=$(STANDARD_MANIFEST_URL) envsubst '$$DEFAULT_CATALOG,$$CERT_MGR_VERSION,$$INSTALL_DEFAULT_CATALOGS,$$MANIFEST' < scripts/install.tpl.sh > $(STANDARD_RELEASE_INSTALL) + MANIFEST=$(EXPERIMENTAL_MANIFEST_URL) envsubst '$$DEFAULT_CATALOG,$$CERT_MGR_VERSION,$$INSTALL_DEFAULT_CATALOGS,$$MANIFEST' < scripts/install.tpl.sh > $(EXPERIMENTAL_RELEASE_INSTALL) ##@ Docs .PHONY: crd-ref-docs -API_REFERENCE_FILENAME := operator-controller-api-reference.md -crd-ref-docs: $(CRD_REF_DOCS) - rm -f $(ROOT_DIR)/docs/refs/api/$(API_REFERENCE_FILENAME) - $(CRD_REF_DOCS) --source-path=$(ROOT_DIR)/api \ - --config=$(ROOT_DIR)/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml \ - --renderer=markdown \ - --output-path=$(ROOT_DIR)/docs/refs/api/$(API_REFERENCE_FILENAME) +API_REFERENCE_FILENAME := olmv1-api-reference.md +API_REFERENCE_DIR := $(ROOT_DIR)/docs/api-reference +crd-ref-docs: $(CRD_REF_DOCS) #EXHELP Generate the API Reference Documents. + rm -f $(API_REFERENCE_DIR)/$(API_REFERENCE_FILENAME) + $(CRD_REF_DOCS) --source-path=$(ROOT_DIR)/api/ \ + --config=$(API_REFERENCE_DIR)/crd-ref-docs-gen-config.yaml \ + --renderer=markdown --output-path=$(API_REFERENCE_DIR)/$(API_REFERENCE_FILENAME); VENVDIR := $(abspath docs/.venv) .PHONY: build-docs build-docs: venv . $(VENV)/activate; \ - mkdocs build + mkdocs build --strict .PHONY: serve-docs serve-docs: venv @@ -323,6 +488,15 @@ serve-docs: venv .PHONY: deploy-docs deploy-docs: venv . $(VENV)/activate; \ - mkdocs gh-deploy --force + mkdocs gh-deploy --force --strict + +# The demo script requires to install asciinema with: brew install asciinema to run on mac os envs. +# Please ensure that all demos are named with the demo name and the suffix -demo-script.sh +.PHONY: update-demos #EXHELP Update and upload the demos. +update-demos: + @for script in hack/demo/*-demo-script.sh; do \ + nm=$$(basename $$script -script.sh); \ + ./hack/demo/generate-asciidemo.sh -u -n $$nm $$(basename $$script); \ + done include Makefile.venv diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..b7e9325fea --- /dev/null +++ b/OWNERS @@ -0,0 +1,4 @@ +approvers: + - olmv1-approvers +reviewers: + - olmv1-reviewers diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES new file mode 100644 index 0000000000..1776b9b654 --- /dev/null +++ b/OWNERS_ALIASES @@ -0,0 +1,50 @@ +aliases: + olmv1-approvers: + - joelanford + - kevinrizza + - perdasilva + - tmshort + + olmv1-reviewers: + - anik120 + - ankitathomas + - bentito + - camilamacedo86 + - dtfranz + - grokspawn + - joelanford + - oceanc80 + - OchiengEd + - perdasilva + - rashmigottipati + - thetechnick + - tmshort + - trgeiger + - pedjak + + api-approvers: + - grokspawn + - thetechnick + + catalogd-approvers: + - grokspawn + + operator-controller-approvers: + - thetechnick + + cmd-approvers: + - grokspawn + + manifest-approvers: + - camilamacedo86 + + ci-approvers: + - camilamacedo86 + + docs-approvers: + - michaelryanpeter + + docs-draft-approvers: + - camilamacedo86 + - grokspawn + - thetechnick diff --git a/PROJECT b/PROJECT index 50ac542dca..a307347a46 100644 --- a/PROJECT +++ b/PROJECT @@ -11,8 +11,8 @@ resources: domain: operatorframework.io group: olm kind: ClusterExtension - path: github.com/operator-framework/operator-controller/api/v1alpha1 - version: v1alpha1 + path: github.com/operator-framework/operator-controller/api/v1 + version: v1 - api: crdVersion: v1 namespaced: true @@ -20,6 +20,6 @@ resources: domain: operatorframework.io group: olm kind: Extension - path: github.com/operator-framework/operator-controller/api/v1alpha1 - version: v1alpha1 + path: github.com/operator-framework/operator-controller/api/v1 + version: v1 version: "3" diff --git a/README.md b/README.md index 2754862092..4be7f30d08 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,182 @@ +[![unit-test](https://github.com/operator-framework/operator-controller/actions/workflows/unit-test.yaml/badge.svg)](https://github.com/operator-framework/operator-controller/actions/workflows/unit-test.yaml) +[![e2e](https://github.com/operator-framework/operator-controller/actions/workflows/e2e.yaml/badge.svg)](https://github.com/operator-framework/operator-controller/actions/workflows/e2e.yaml) +[![codecov](https://codecov.io/gh/operator-framework/operator-controller/graph/badge.svg?token=5f34zaWaN7)](https://codecov.io/gh/operator-framework/operator-controller) + # operator-controller The operator-controller is the central component of Operator Lifecycle Manager (OLM) v1. It extends Kubernetes with an API through which users can install extensions. -## Mission +## Overview + +OLM v1 is the follow-up to [OLM v0](https://github.com/operator-framework/operator-lifecycle-manager). Its purpose is to provide APIs, +controllers, and tooling that support the packaging, distribution, and lifecycling of Kubernetes extensions. It aims to: -OLM’s purpose is to provide APIs, controllers, and tooling that support the packaging, distribution, and lifecycling of Kubernetes extensions. It aims to: - align with Kubernetes designs and user assumptions - provide secure, high-quality, and predictable user experiences centered around declarative GitOps concepts - give cluster admins the minimal necessary controls to build their desired cluster architectures and to have ultimate control -## Overview - -OLM v1 is the follow-up to OLM v0, located [here](https://github.com/operator-framework/operator-lifecycle-manager). - OLM v1 consists of two different components: -* operator-controller (this repository) -* [catalogd](https://github.com/operator-framework/catalogd) - -For a more complete overview of OLM v1 and how it differs from OLM v0, see our [overview](docs/olmv1_overview.md). - -### Installation - -The following script will install OLMv1 on a Kubernetes cluster. If you don't have one, you can deploy a Kubernetes cluster with [KIND](https://sigs.k8s.io/kind). - -> [!CAUTION] -> Operator-Controller depends on [cert-manager](https://cert-manager.io/). Running the following command -> may affect an existing installation of cert-manager and cause cluster instability. - -The latest version of Operator Controller can be installed with the following command: - -```bash -curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s -``` - -## Getting Started with OLM v1 - -This quickstart procedure will guide you through the following processes: -* Deploying a catalog -* Installing, upgrading, or downgrading an extension -* Deleting catalogs and extensions - -### Create a Catalog - -OLM v1 is designed to source content from an on-cluster catalog in the file-based catalog ([FBC](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs)) format. -These catalogs are deployed and configured through the `ClusterCatalog` resource. More information on adding catalogs -can be found [here](./docs/Tasks/adding-a-catalog). - -The following example uses the official [OperatorHub](https://operatorhub.io) catalog that contains many different -extensions to choose from. Note that this catalog contains packages designed to work with OLM v0, and that not all packages -will work with OLM v1. More information on catalog exploration and content compatibility can be found [here](./docs/refs/catalog-queries.md). - -To create the catalog, run the following command: - -```bash -# Create ClusterCatalog -kubectl apply -f - < + API Version: olm.operatorframework.io/v1 + Kind: ClusterCatalog + Metadata: + Creation Timestamp: 2024-10-17T13:48:46Z + Finalizers: + olm.operatorframework.io/delete-server-cache + Generation: 1 + Resource Version: 7908 + UID: 34eeaa91-9f8e-4254-9937-0ae9d25e92df + Spec: + Availability Mode: Available + Priority: 0 + Source: + Image: + Ref: quay.io/operatorhubio/catalog:latest + Type: Image + Status: + Conditions: + Last Transition Time: 2024-10-17T13:48:59Z + Message: Successfully unpacked and stored content from resolved source + Observed Generation: 1 + Reason: Succeeded + Status: False + Type: Progressing + Last Transition Time: 2024-10-17T13:48:59Z + Message: Serving desired content from resolved source + Observed Generation: 1 + Reason: Available + Status: True + Type: Serving + Last Unpacked: 2024-10-17T13:48:58Z + Resolved Source: + Image: + Last Successful Poll Attempt: 2024-10-17T14:49:59Z + Ref: quay.io/operatorhubio/catalog@sha256:82be554b15ff246d8cc428f8d2f4cf5857c02ce3225d95d92a769ea3095e1fc7 + Type: Image + Urls: + Base: https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio + Events: + ``` + +1. Port forward the `catalogd-service` service in the `olmv1-system` namespace: + ```sh + $ kubectl -n olmv1-system port-forward svc/catalogd-service 8080:443 + ``` + +1. Access the `v1/all` service endpoint and filter the results to a list of packages by running the following command: + + ```sh + $ curl https://localhost:8080/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.package") | .name' + ``` + + *Example output* + ```sh + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 100 110M 100 110M 0 0 112M 0 --:--:-- --:--:-- --:--:-- 112M + "ack-acm-controller" + "ack-apigatewayv2-controller" + "ack-applicationautoscaling-controller" + "ack-cloudtrail-controller" + "ack-cloudwatch-controller" + "ack-dynamodb-controller" + "ack-ec2-controller" + "ack-ecr-controller" + "ack-eks-controller" + "ack-elasticache-controller" + "ack-emrcontainers-controller" + "ack-eventbridge-controller" + "ack-iam-controller" + "ack-kinesis-controller" + ... + ``` +1. Run the following command to get a list of channels for the `ack-acm-controller` package: + + ```sh + $ curl https://localhost:8080/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.channel") | select(.package == "ack-acm-controller") | .name' + ``` + + *Example output* + ```sh + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 100 110M 100 110M 0 0 115M 0 --:--:-- --:--:-- --:--:-- 116M + "alpha" + ``` + +1. Run the following command to get a list of bundles belonging to the `ack-acm-controller` package: + + ```sh + $ curl https://localhost:8080/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.bundle") | select(.package == "ack-acm-controller") | .name' + ``` + + *Example output* + ```sh + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 100 110M 100 110M 0 0 122M 0 --:--:-- --:--:-- --:--:-- 122M + "ack-acm-controller.v0.0.1" + "ack-acm-controller.v0.0.2" + "ack-acm-controller.v0.0.4" + "ack-acm-controller.v0.0.5" + "ack-acm-controller.v0.0.6" + "ack-acm-controller.v0.0.7" + ``` ## License diff --git a/RELEASE.md b/RELEASE.md index 093f1fd2e0..5d8a129fa8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,33 +2,40 @@ ## Choosing version increment -The `operator-controller` project is in its initial development phase and has yet to have a Major release, so users should assume that breaking changes may be seen in Minor releases as defined [here](https://semver.org/#spec-item-4). +The `operator-controller` following Semantic Versioning guarantees: -In general, the `operator-controller`` will only support Minor releases until it has reached a degree of stability and adoption that the benefits of supporting Patch releases outweighs the costs of supporting this release workflow. If a member of the community strongly desires a patch release addressing a critical bug, they should submit an issue and it will be considered on a case-by-case basis. - -In the future, the `operator-controller` will have a Major release in which we'll adopt the following Semantic Versioning guarantees: * Major: API breaking change(s) are made. * Minor: Backwards compatible features are added. * Patch: Backwards compatible bug fix is made. -When a Major or Minor release being made is associated with one or more milestones, please ensure that all related features have been merged into the `main` branch before continuing. +When a Major or Minor release being made is associated with one or more milestones, +please ensure that all related features have been merged into the `main` branch before continuing. ## Creating the release -Note that throughout this guide, the `upstream` remote refers to the `operator-framework/operator-controller` repository. +Note that throughout this guide, the `upstream` remote refers to the `operator-framework/operator-controller` repository. The release process differs slightly based on whether a patch or major/minor release is being made. ### Patch Release -#### Step 1 -In this example we will be creating a new patch release from version `v1.2.3`, on the branch `release-v1.2`. -First ensure that the release branch has been updated on remote with the changes from the patch, then perform the following: + +In this example, we will be creating a new patch release from version `v1.2.3` on the branch `release-v1.2`. + +#### Step 1 +First, make sure the `release-v1.2` branch is updated with the latest changes from upstream: ```bash git fetch upstream release-v1.2 -git pull release-v1.2 git checkout release-v1.2 +git reset --hard upstream/release-v1.2 ``` #### Step 2 +Run the following command to confirm that your local branch has the latest expected commit: +```bash +git log --oneline -n 5 +``` +Check that the most recent commit matches the latest commit in the upstream `release-v1.2` branch. + +#### Step 3 Create a new tag, incrementing the patch number from the previous version. In this case, we'll be incrementing from `v1.2.3` to `v1.2.4`: ```bash ## Previous version was v1.2.3, so we bump the patch number up by one @@ -56,3 +63,21 @@ git push upstream v1.2.0 ### Post-Steps Once the tag has been pushed the release action should run automatically. You can view the progress [here](https://github.com/operator-framework/operator-controller/actions/workflows/release.yaml). When finished, the release should then be available on the [releases page](https://github.com/operator-framework/operator-controller/releases). + + +## Backporting Policy + +Significant security and critical bug fixes can be backported to the most recent minor release. +Special backport requests can be discussed during the weekly Community meeting or via Slack channel; +this does not guarantee an exceptional backport will be created. + +Occasionally non-critical issue fixes will be backported, either at an approver’s discretion or by request as noted above. +If you believe an issue should be backported, please feel free to reach out and raise your concerns or needs. +For information on contacting maintainers via the [#olm-dev](https://kubernetes.slack.com/archives/C0181L6JYQ2) Slack channel +and attending meetings. To know more about see [How to Contribute](./CONTRIBUTING.md). + +### Process + +1. Create a PR with the fix cherry-picked into the release branch +2. Ask for a review from the maintainers. +3. Once approved, merge the PR and perform the Patch Release steps above. \ No newline at end of file diff --git a/Tiltfile b/Tiltfile index 3302129023..d736b8f94d 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,23 +1,23 @@ -if not os.path.exists('../tilt-support'): - fail('Please clone https://github.com/operator-framework/tilt-support to ../tilt-support') +load('.tilt-support', 'deploy_repo') -load('../tilt-support/Tiltfile', 'deploy_repo') - -config.define_string_list('repos', args=True) -cfg = config.parse() -repos = cfg.get('repos', ['operator-controller', 'catalogd']) - -repo = { - 'image': 'quay.io/operator-framework/operator-controller', - 'yaml': 'config/overlays/cert-manager', - 'binaries': { - 'manager': 'operator-controller-controller-manager', +olmv1 = { + 'repos': { + 'catalogd': { + 'image': 'quay.io/operator-framework/catalogd', + 'binary': './cmd/catalogd', + 'deployment': 'catalogd-controller-manager', + 'deps': ['api', 'cmd/catalogd', 'internal/catalogd', 'internal/shared', 'go.mod', 'go.sum'], + 'starting_debug_port': 20000, + }, + 'operator-controller': { + 'image': 'quay.io/operator-framework/operator-controller', + 'binary': './cmd/operator-controller', + 'deployment': 'operator-controller-controller-manager', + 'deps': ['api', 'cmd/operator-controller', 'internal/operator-controller', 'internal/shared', 'go.mod', 'go.sum'], + 'starting_debug_port': 30000, + }, }, - 'starting_debug_port': 30000, + 'yaml': 'helm/tilt.yaml', } -for r in repos: - if r == 'operator-controller': - deploy_repo('operator-controller', repo) - else: - include('../{}/Tiltfile'.format(r)) +deploy_repo(olmv1, '-tags containers_image_openpgp') diff --git a/api/OWNERS b/api/OWNERS new file mode 100644 index 0000000000..71df7cfc52 --- /dev/null +++ b/api/OWNERS @@ -0,0 +1,2 @@ +approvers: + - api-approvers diff --git a/api/v1/clustercatalog_types.go b/api/v1/clustercatalog_types.go new file mode 100644 index 0000000000..c18fa3c7e6 --- /dev/null +++ b/api/v1/clustercatalog_types.go @@ -0,0 +1,352 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SourceType defines the type of source used for catalogs. +// +enum +type SourceType string + +// AvailabilityMode defines the availability of the catalog +type AvailabilityMode string + +const ( + SourceTypeImage SourceType = "Image" + + MetadataNameLabel = "olm.operatorframework.io/metadata.name" + + AvailabilityModeAvailable AvailabilityMode = "Available" + AvailabilityModeUnavailable AvailabilityMode = "Unavailable" + + // Condition types + TypeServing = "Serving" + + // Serving Reasons + ReasonAvailable = "Available" + ReasonUnavailable = "Unavailable" + ReasonUserSpecifiedUnavailable = "UserSpecifiedUnavailable" +) + +//+kubebuilder:object:root=true +//+kubebuilder:resource:scope=Cluster +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name=LastUnpacked,type=date,JSONPath=`.status.lastUnpacked` +//+kubebuilder:printcolumn:name="Serving",type=string,JSONPath=`.status.conditions[?(@.type=="Serving")].status` +//+kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp` + +// ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. +// For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs +type ClusterCatalog struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ObjectMeta `json:"metadata"` + + // spec is the desired state of the ClusterCatalog. + // spec is required. + // The controller will work to ensure that the desired + // catalog is unpacked and served over the catalog content HTTP server. + // +kubebuilder:validation:Required + Spec ClusterCatalogSpec `json:"spec"` + + // status contains information about the state of the ClusterCatalog such as: + // - Whether or not the catalog contents are being served via the catalog content HTTP server + // - Whether or not the ClusterCatalog is progressing to a new state + // - A reference to the source from which the catalog contents were retrieved + // +optional + Status ClusterCatalogStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// ClusterCatalogList contains a list of ClusterCatalog +type ClusterCatalogList struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ListMeta `json:"metadata"` + + // items is a list of ClusterCatalogs. + // items is required. + // +kubebuilder:validation:Required + Items []ClusterCatalog `json:"items"` +} + +// ClusterCatalogSpec defines the desired state of ClusterCatalog +type ClusterCatalogSpec struct { + // source allows a user to define the source of a catalog. + // A "catalog" contains information on content that can be installed on a cluster. + // Providing a catalog source makes the contents of the catalog discoverable and usable by + // other on-cluster components. + // These on-cluster components may do a variety of things with this information, such as + // presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + // The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + // For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + // source is a required field. + // + // Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + // + // source: + // type: Image + // image: + // ref: quay.io/operatorhubio/catalog:latest + // + // +kubebuilder:validation:Required + Source CatalogSource `json:"source"` + + // priority allows the user to define a priority for a ClusterCatalog. + // priority is optional. + // + // A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + // A higher number means higher priority. + // + // It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + // When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + // + // When omitted, the default priority is 0 because that is the zero value of integers. + // + // Negative numbers can be used to specify a priority lower than the default. + // Positive numbers can be used to specify a priority higher than the default. + // + // The lowest possible value is -2147483648. + // The highest possible value is 2147483647. + // + // +kubebuilder:default:=0 + // +kubebuilder:validation:minimum:=-2147483648 + // +kubebuilder:validation:maximum:=2147483647 + // +optional + Priority int32 `json:"priority"` + + // availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + // availabilityMode is optional. + // + // Allowed values are "Available" and "Unavailable" and omitted. + // + // When omitted, the default value is "Available". + // + // When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + // Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + // and its contents as usable. + // + // When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + // When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + // Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + // to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + // + // +kubebuilder:validation:Enum:="Unavailable";"Available" + // +kubebuilder:default:="Available" + // +optional + AvailabilityMode AvailabilityMode `json:"availabilityMode,omitempty"` +} + +// ClusterCatalogStatus defines the observed state of ClusterCatalog +type ClusterCatalogStatus struct { + // conditions is a representation of the current state for this ClusterCatalog. + // + // The current condition types are Serving and Progressing. + // + // The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + // When it has a status of True and a reason of Available, the contents of the catalog are being served. + // When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + // When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + // + // The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + // When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + // When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + // When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + // + // In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + // catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + // contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + // to the contents we identify that there are updates to the contents. + // + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + // resolvedSource contains information about the resolved source based on the source type. + // +optional + ResolvedSource *ResolvedCatalogSource `json:"resolvedSource,omitempty"` + // urls contains the URLs that can be used to access the catalog. + // +optional + URLs *ClusterCatalogURLs `json:"urls,omitempty"` + // lastUnpacked represents the last time the contents of the + // catalog were extracted from their source format. As an example, + // when using an Image source, the OCI image will be pulled and the + // image layers written to a file-system backed cache. We refer to the + // act of this extraction from the source format as "unpacking". + // +optional + LastUnpacked *metav1.Time `json:"lastUnpacked,omitempty"` +} + +// ClusterCatalogURLs contains the URLs that can be used to access the catalog. +type ClusterCatalogURLs struct { + // base is a cluster-internal URL that provides endpoints for + // accessing the content of the catalog. + // + // It is expected that clients append the path for the endpoint they wish + // to access. + // + // Currently, only a single endpoint is served and is accessible at the path + // /api/v1. + // + // The endpoints served for the v1 API are: + // - /all - this endpoint returns the entirety of the catalog contents in the FBC format + // + // As the needs of users and clients of the evolve, new endpoints may be added. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength:=525 + // +kubebuilder:validation:XValidation:rule="isURL(self)",message="must be a valid URL" + // +kubebuilder:validation:XValidation:rule="isURL(self) ? (url(https://github.com/self).getScheme() == \"http\" || url(https://github.com/self).getScheme() == \"https\") : true",message="scheme must be either http or https" + Base string `json:"base"` +} + +// CatalogSource is a discriminated union of possible sources for a Catalog. +// CatalogSource contains the sourcing information for a Catalog +// +union +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Image' ? has(self.image) : !has(self.image)",message="image is required when source type is Image, and forbidden otherwise" +type CatalogSource struct { + // type is a reference to the type of source the catalog is sourced from. + // type is required. + // + // The only allowed value is "Image". + // + // When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + // When using an image source, the image field must be set and must be the only field defined for this type. + // + // +unionDiscriminator + // +kubebuilder:validation:Enum:="Image" + // +kubebuilder:validation:Required + Type SourceType `json:"type"` + // image is used to configure how catalog contents are sourced from an OCI image. + // This field is required when type is Image, and forbidden otherwise. + // +optional + Image *ImageSource `json:"image,omitempty"` +} + +// ResolvedCatalogSource is a discriminated union of resolution information for a Catalog. +// ResolvedCatalogSource contains the information about a sourced Catalog +// +union +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Image' ? has(self.image) : !has(self.image)",message="image is required when source type is Image, and forbidden otherwise" +type ResolvedCatalogSource struct { + // type is a reference to the type of source the catalog is sourced from. + // type is required. + // + // The only allowed value is "Image". + // + // When set to "Image", information about the resolved image source will be set in the 'image' field. + // + // +unionDiscriminator + // +kubebuilder:validation:Enum:="Image" + // +kubebuilder:validation:Required + Type SourceType `json:"type"` + // image is a field containing resolution information for a catalog sourced from an image. + // This field must be set when type is Image, and forbidden otherwise. + Image *ResolvedImageSource `json:"image"` +} + +// ResolvedImageSource provides information about the resolved source of a Catalog sourced from an image. +type ResolvedImageSource struct { + // ref contains the resolved image digest-based reference. + // The digest format is used so users can use other tooling to fetch the exact + // OCI manifests that were used to extract the catalog contents. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength:=1000 + // +kubebuilder:validation:XValidation:rule="self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')",message="must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character." + // +kubebuilder:validation:XValidation:rule="self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\"",message="a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters." + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\"",message="must end with a digest" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find('(@.*:)').matches('(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])') : true",message="digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters." + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').substring(1).size() >= 32 : true",message="digest is not valid. the encoded string must be at least 32 characters" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').matches(':[0-9A-Fa-f]*$') : true",message="digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)" + Ref string `json:"ref"` +} + +// ImageSource enables users to define the information required for sourcing a Catalog from an OCI image +// +// If we see that there is a possibly valid digest-based image reference AND pollIntervalMinutes is specified, +// reject the resource since there is no use in polling a digest-based image reference. +// +kubebuilder:validation:XValidation:rule="self.ref.find('(@.*:)') != \"\" ? !has(self.pollIntervalMinutes) : true",message="cannot specify pollIntervalMinutes while using digest-based image" +type ImageSource struct { + // ref allows users to define the reference to a container image containing Catalog contents. + // ref is required. + // ref can not be more than 1000 characters. + // + // A reference can be broken down into 3 parts - the domain, name, and identifier. + // + // The domain is typically the registry where an image is located. + // It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + // Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + // Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + // The port must be the last value in the domain. + // Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + // + // The name is typically the repository in the registry where an image is located. + // It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + // Multiple names can be concatenated with the "/" character. + // The domain and name are combined using the "/" character. + // Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + // An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + // + // The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + // It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + // For a digest-based reference, the "@" character is the separator. + // For a tag-based reference, the ":" character is the separator. + // An identifier is required in the reference. + // + // Digest-based references must contain an algorithm reference immediately after the "@" separator. + // The algorithm reference must be followed by the ":" character and an encoded string. + // The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + // Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + // The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + // + // Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + // The tag must not be longer than 127 characters. + // + // An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + // An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength:=1000 + // +kubebuilder:validation:XValidation:rule="self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')",message="must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character." + // +kubebuilder:validation:XValidation:rule="self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\"",message="a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters." + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" || self.find(':.*$') != \"\"",message="must end with a digest or a tag" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').substring(1).size() <= 127 : true) : true",message="tag is invalid. the tag must not be more than 127 characters" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').matches(':[\\\\w][\\\\w.-]*$') : true) : true",message="tag is invalid. valid tags must begin with a word character (alphanumeric + \"_\") followed by word characters or \".\", and \"-\" characters" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find('(@.*:)').matches('(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])') : true",message="digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters." + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').substring(1).size() >= 32 : true",message="digest is not valid. the encoded string must be at least 32 characters" + // +kubebuilder:validation:XValidation:rule="self.find('(@.*:)') != \"\" ? self.find(':.*$').matches(':[0-9A-Fa-f]*$') : true",message="digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)" + Ref string `json:"ref"` + + // pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + // pollIntervalMinutes is optional. + // pollIntervalMinutes can not be specified when ref is a digest-based reference. + // + // When omitted, the image will not be polled for new content. + // +kubebuilder:validation:Minimum:=1 + // +optional + PollIntervalMinutes *int `json:"pollIntervalMinutes,omitempty"` +} + +func init() { + SchemeBuilder.Register(&ClusterCatalog{}, &ClusterCatalogList{}) +} diff --git a/api/v1/clustercatalog_types_test.go b/api/v1/clustercatalog_types_test.go new file mode 100644 index 0000000000..71a64bc9e9 --- /dev/null +++ b/api/v1/clustercatalog_types_test.go @@ -0,0 +1,452 @@ +package v1 + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" + "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + celconfig "k8s.io/apiserver/pkg/apis/cel" + "k8s.io/utils/ptr" + "sigs.k8s.io/yaml" +) + +const crdFilePath = "../../helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml" + +func TestImageSourceCELValidationRules(t *testing.T) { + validators := fieldValidatorsFromFile(t, crdFilePath) + pth := "openAPIV3Schema.properties.spec.properties.source.properties.image" + validator, found := validators[GroupVersion.Version][pth] + require.True(t, found) + + for name, tc := range map[string]struct { + spec ImageSource + wantErrs []string + }{ + "valid digest based image ref, poll interval not allowed, poll interval specified": { + spec: ImageSource{ + Ref: "docker.io/test-image@sha256:abcdef123456789abcdef123456789abc", + PollIntervalMinutes: ptr.To(1), + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image: Invalid value: \"object\": cannot specify pollIntervalMinutes while using digest-based image", + }, + }, + "valid digest based image ref, poll interval not allowed, poll interval not specified": { + spec: ImageSource{ + Ref: "docker.io/test-image@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{}, + }, + "invalid digest based image ref, invalid domain": { + spec: ImageSource{ + Ref: "-quay+docker/foo/bar@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character.", + }, + }, + "invalid digest based image ref, invalid name": { + spec: ImageSource{ + Ref: "docker.io/FOO/BAR@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.", + }, + }, + "invalid digest based image ref, invalid digest algorithm": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@99-problems:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters.", + }, + }, + "invalid digest based image ref, too short digest encoding": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@sha256:abcdef123456789", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": digest is not valid. the encoded string must be at least 32 characters", + }, + }, + "invalid digest based image ref, invalid characters in digest encoding": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@sha256:XYZxy123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)", + }, + }, + "invalid image ref, no tag or digest": { + spec: ImageSource{ + Ref: "docker.io/foo/bar", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": must end with a digest or a tag", + }, + }, + "invalid tag based image ref, tag too long": { + spec: ImageSource{ + Ref: fmt.Sprintf("docker.io/foo/bar:%s", strings.Repeat("x", 128)), + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": tag is invalid. the tag must not be more than 127 characters", + }, + }, + "invalid tag based image ref, tag contains invalid characters": { + spec: ImageSource{ + Ref: "docker.io/foo/bar:-foo_bar-", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": tag is invalid. valid tags must begin with a word character (alphanumeric + \"_\") followed by word characters or \".\", and \"-\" characters", + }, + }, + "valid tag based image ref": { + spec: ImageSource{ + Ref: "docker.io/foo/bar:v1.0.0", + }, + wantErrs: []string{}, + }, + "valid tag based image ref, pollIntervalMinutes specified": { + spec: ImageSource{ + Ref: "docker.io/foo/bar:v1.0.0", + PollIntervalMinutes: ptr.To(5), + }, + wantErrs: []string{}, + }, + "invalid image ref, only domain with port": { + spec: ImageSource{ + Ref: "docker.io:8080", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.spec.properties.source.properties.image.ref: Invalid value: \"string\": a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.", + }, + }, + "valid image ref, domain with port": { + spec: ImageSource{ + Ref: "my-subdomain.docker.io:8080/foo/bar:latest", + }, + wantErrs: []string{}, + }, + "valid image ref, tag ends with hyphen": { + spec: ImageSource{ + Ref: "my-subdomain.docker.io:8080/foo/bar:latest-", + }, + wantErrs: []string{}, + }, + } { + t.Run(name, func(t *testing.T) { + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.spec) //nolint:gosec + require.NoError(t, err) + errs := validator(obj, nil) + require.Len(t, errs, len(tc.wantErrs), "want", tc.wantErrs, "got", errs) + for i := range tc.wantErrs { + got := errs[i].Error() + assert.Equal(t, tc.wantErrs[i], got) + } + }) + } +} + +func TestResolvedImageSourceCELValidation(t *testing.T) { + validators := fieldValidatorsFromFile(t, crdFilePath) + pth := "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref" + validator, found := validators[GroupVersion.Version][pth] + require.True(t, found) + + for name, tc := range map[string]struct { + spec ImageSource + wantErrs []string + }{ + "valid digest based image ref": { + spec: ImageSource{ + Ref: "docker.io/test-image@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{}, + }, + "invalid digest based image ref, invalid domain": { + spec: ImageSource{ + Ref: "-quay+docker/foo/bar@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character.", + }, + }, + "invalid digest based image ref, invalid name": { + spec: ImageSource{ + Ref: "docker.io/FOO/BAR@sha256:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.", + }, + }, + "invalid digest based image ref, invalid digest algorithm": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@99-problems:abcdef123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters.", + }, + }, + "invalid digest based image ref, too short digest encoding": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@sha256:abcdef123456789", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": digest is not valid. the encoded string must be at least 32 characters", + }, + }, + "invalid digest based image ref, invalid characters in digest encoding": { + spec: ImageSource{ + Ref: "docker.io/foo/bar@sha256:XYZxy123456789abcdef123456789abc", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)", + }, + }, + "invalid image ref, no digest": { + spec: ImageSource{ + Ref: "docker.io/foo/bar", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": must end with a digest", + }, + }, + "invalid image ref, only domain with port": { + spec: ImageSource{ + Ref: "docker.io:8080", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.", + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": must end with a digest", + }, + }, + "invalid image ref, tag-based ref": { + spec: ImageSource{ + Ref: "docker.io/foo/bar:latest", + }, + wantErrs: []string{ + "openAPIV3Schema.properties.status.properties.resolvedSource.properties.image.properties.ref: Invalid value: \"string\": must end with a digest", + }, + }, + } { + t.Run(name, func(t *testing.T) { + errs := validator(tc.spec.Ref, nil) + require.Len(t, errs, len(tc.wantErrs), "want", tc.wantErrs, "got", errs) + for i := range tc.wantErrs { + got := errs[i].Error() + assert.Equal(t, tc.wantErrs[i], got) + } + }) + } +} + +func TestClusterCatalogURLsCELValidation(t *testing.T) { + validators := fieldValidatorsFromFile(t, crdFilePath) + pth := "openAPIV3Schema.properties.status.properties.urls.properties.base" + validator, found := validators[GroupVersion.Version][pth] + require.True(t, found) + for name, tc := range map[string]struct { + urls ClusterCatalogURLs + wantErrs []string + }{ + "base is valid": { + urls: ClusterCatalogURLs{ + Base: "https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio", + }, + wantErrs: []string{}, + }, + "base is invalid, scheme is not one of http or https": { + urls: ClusterCatalogURLs{ + Base: "file://somefilepath", + }, + wantErrs: []string{ + fmt.Sprintf("%s: Invalid value: \"string\": scheme must be either http or https", pth), + }, + }, + "base is invalid": { + urls: ClusterCatalogURLs{ + Base: "notevenarealURL", + }, + wantErrs: []string{ + fmt.Sprintf("%s: Invalid value: \"string\": must be a valid URL", pth), + }, + }, + } { + t.Run(name, func(t *testing.T) { + errs := validator(tc.urls.Base, nil) + fmt.Println(errs) + require.Len(t, errs, len(tc.wantErrs)) + for i := range tc.wantErrs { + got := errs[i].Error() + assert.Equal(t, tc.wantErrs[i], got) + } + }) + } +} + +func TestSourceCELValidation(t *testing.T) { + validators := fieldValidatorsFromFile(t, crdFilePath) + pth := "openAPIV3Schema.properties.spec.properties.source" + validator, found := validators[GroupVersion.Version][pth] + require.True(t, found) + for name, tc := range map[string]struct { + source CatalogSource + wantErrs []string + }{ + "image source missing required image field": { + source: CatalogSource{ + Type: SourceTypeImage, + }, + wantErrs: []string{ + fmt.Sprintf("%s: Invalid value: \"object\": image is required when source type is %s, and forbidden otherwise", pth, SourceTypeImage), + }, + }, + "image source with required image field": { + source: CatalogSource{ + Type: SourceTypeImage, + Image: &ImageSource{ + Ref: "docker.io/foo/bar:latest", + }, + }, + wantErrs: []string{}, + }, + } { + t.Run(name, func(t *testing.T) { + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.source) //nolint:gosec + require.NoError(t, err) + errs := validator(obj, nil) + fmt.Println(errs) + require.Len(t, errs, len(tc.wantErrs)) + for i := range tc.wantErrs { + got := errs[i].Error() + assert.Equal(t, tc.wantErrs[i], got) + } + }) + } +} + +func TestResolvedSourceCELValidation(t *testing.T) { + validators := fieldValidatorsFromFile(t, crdFilePath) + pth := "openAPIV3Schema.properties.status.properties.resolvedSource" + validator, found := validators[GroupVersion.Version][pth] + + require.True(t, found) + for name, tc := range map[string]struct { + source ResolvedCatalogSource + wantErrs []string + }{ + "image source missing required image field": { + source: ResolvedCatalogSource{ + Type: SourceTypeImage, + }, + wantErrs: []string{ + fmt.Sprintf("%s: Invalid value: \"object\": image is required when source type is %s, and forbidden otherwise", pth, SourceTypeImage), + }, + }, + "image source with required image field": { + source: ResolvedCatalogSource{ + Type: SourceTypeImage, + Image: &ResolvedImageSource{ + Ref: "docker.io/foo/bar@sha256:abcdef123456789abcdef123456789abc", + }, + }, + wantErrs: []string{}, + }, + } { + t.Run(name, func(t *testing.T) { + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.source) //nolint:gosec + require.NoError(t, err) + errs := validator(obj, nil) + require.Len(t, errs, len(tc.wantErrs)) + for i := range tc.wantErrs { + got := errs[i].Error() + assert.Equal(t, tc.wantErrs[i], got) + } + }) + } +} + +// fieldValidatorsFromFile extracts the CEL validators by version and JSONPath from a CRD file and returns +// a validator func for testing against samples. +// nolint:unparam +func fieldValidatorsFromFile(t *testing.T, crdFilePath string) map[string]map[string]CELValidateFunc { + data, err := os.ReadFile(crdFilePath) + require.NoError(t, err) + + var crd apiextensionsv1.CustomResourceDefinition + err = yaml.Unmarshal(data, &crd) + require.NoError(t, err) + + ret := map[string]map[string]CELValidateFunc{} + for _, v := range crd.Spec.Versions { + var internalSchema apiextensions.JSONSchemaProps + err := apiextensionsv1.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(v.Schema.OpenAPIV3Schema, &internalSchema, nil) + require.NoError(t, err, "failed to convert JSONSchemaProps for version %s: %v", v.Name, err) + structuralSchema, err := schema.NewStructural(&internalSchema) + require.NoError(t, err, "failed to create StructuralSchema for version %s: %v", v.Name, err) + + versionVals, err := findCEL(structuralSchema, true, field.NewPath("openAPIV3Schema")) + require.NoError(t, err, "failed to find CEL for version %s: %v", v.Name, err) + ret[v.Name] = versionVals + } + return ret +} + +// CELValidateFunc tests a sample object against a CEL validator. +type CELValidateFunc func(obj, old interface{}) field.ErrorList + +func findCEL(s *schema.Structural, root bool, pth *field.Path) (map[string]CELValidateFunc, error) { + ret := map[string]CELValidateFunc{} + + if len(s.XValidations) > 0 { + s := *s + pth := *pth + ret[pth.String()] = func(obj, old interface{}) field.ErrorList { + errs, _ := cel.NewValidator(&s, root, celconfig.PerCallLimit).Validate(context.TODO(), &pth, &s, obj, old, celconfig.RuntimeCELCostBudget) + return errs + } + } + + for k, v := range s.Properties { + v := v + sub, err := findCEL(&v, false, pth.Child("properties").Child(k)) + if err != nil { + return nil, err + } + + for pth, val := range sub { + ret[pth] = val + } + } + if s.Items != nil { + sub, err := findCEL(s.Items, false, pth.Child("items")) + if err != nil { + return nil, err + } + for pth, val := range sub { + ret[pth] = val + } + } + if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil { + sub, err := findCEL(s.AdditionalProperties.Structural, false, pth.Child("additionalProperties")) + if err != nil { + return nil, err + } + for pth, val := range sub { + ret[pth] = val + } + } + + return ret, nil +} diff --git a/api/v1/clusterextension_types.go b/api/v1/clusterextension_types.go new file mode 100644 index 0000000000..6de62b0e12 --- /dev/null +++ b/api/v1/clusterextension_types.go @@ -0,0 +1,555 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ClusterExtensionKind = "ClusterExtension" + +type ( + UpgradeConstraintPolicy string + CRDUpgradeSafetyEnforcement string + + ClusterExtensionConfigType string +) + +const ( + // The extension will only upgrade if the new version satisfies + // the upgrade constraints set by the package author. + UpgradeConstraintPolicyCatalogProvided UpgradeConstraintPolicy = "CatalogProvided" + + // Unsafe option which allows an extension to be + // upgraded or downgraded to any available version of the package and + // ignore the upgrade path designed by package authors. + // This assumes that users independently verify the outcome of the changes. + // Use with caution as this can lead to unknown and potentially + // disastrous results such as data loss. + UpgradeConstraintPolicySelfCertified UpgradeConstraintPolicy = "SelfCertified" + + ClusterExtensionConfigTypeInline ClusterExtensionConfigType = "Inline" +) + +// ClusterExtensionSpec defines the desired state of ClusterExtension +type ClusterExtensionSpec struct { + // namespace is a reference to a Kubernetes namespace. + // This is the namespace in which the provided ServiceAccount must exist. + // It also designates the default namespace where namespace-scoped resources + // for the extension are applied to the cluster. + // Some extensions may contain namespace-scoped resources to be applied in other namespaces. + // This namespace must exist. + // + // namespace is required, immutable, and follows the DNS label standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + // start and end with an alphanumeric character, and be no longer than 63 characters + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation:MaxLength:=63 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label" + // +kubebuilder:validation:Required + Namespace string `json:"namespace"` + + // serviceAccount is a reference to a ServiceAccount used to perform all interactions + // with the cluster that are required to manage the extension. + // The ServiceAccount must be configured with the necessary permissions to perform these interactions. + // The ServiceAccount must exist in the namespace referenced in the spec. + // serviceAccount is required. + // + // +kubebuilder:validation:Required + ServiceAccount ServiceAccountReference `json:"serviceAccount"` + + // source is a required field which selects the installation source of content + // for this ClusterExtension. Selection is performed by setting the sourceType. + // + // Catalog is currently the only implemented sourceType, and setting the + // sourcetype to "Catalog" requires the catalog field to also be defined. + // + // Below is a minimal example of a source definition (in yaml): + // + // source: + // sourceType: Catalog + // catalog: + // packageName: example-package + // + // +kubebuilder:validation:Required + Source SourceConfig `json:"source"` + + // install is an optional field used to configure the installation options + // for the ClusterExtension such as the pre-flight check configuration. + // + // +optional + Install *ClusterExtensionInstallConfig `json:"install,omitempty"` + + // config is an optional field used to specify bundle specific configuration + // used to configure the bundle. Configuration is bundle specific and a bundle may provide + // a configuration schema. When not specified, the default configuration of the resolved bundle will be used. + // + // config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide + // a configuration schema the final manifests will be derived on a best-effort basis. More information on how + // to configure the bundle should be found in its end-user documentation. + // + // + // +optional + Config *ClusterExtensionConfig `json:"config,omitempty"` +} + +const SourceTypeCatalog = "Catalog" + +// SourceConfig is a discriminated union which selects the installation source. +// +// +union +// +kubebuilder:validation:XValidation:rule="has(self.sourceType) && self.sourceType == 'Catalog' ? has(self.catalog) : !has(self.catalog)",message="catalog is required when sourceType is Catalog, and forbidden otherwise" +type SourceConfig struct { + // sourceType is a required reference to the type of install source. + // + // Allowed values are "Catalog" + // + // When this field is set to "Catalog", information for determining the + // appropriate bundle of content to install will be fetched from + // ClusterCatalog resources existing on the cluster. + // When using the Catalog sourceType, the catalog field must also be set. + // + // +unionDiscriminator + // +kubebuilder:validation:Enum:="Catalog" + // +kubebuilder:validation:Required + SourceType string `json:"sourceType"` + + // catalog is used to configure how information is sourced from a catalog. + // This field is required when sourceType is "Catalog", and forbidden otherwise. + // + // +optional + Catalog *CatalogFilter `json:"catalog,omitempty"` +} + +// ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config. +// ClusterExtensionInstallConfig requires the namespace and serviceAccount which should be used for the installation of packages. +// +// +kubebuilder:validation:XValidation:rule="has(self.preflight)",message="at least one of [preflight] are required when install is specified" +// +union +type ClusterExtensionInstallConfig struct { + // preflight is an optional field that can be used to configure the checks that are + // run before installation or upgrade of the content for the package specified in the packageName field. + // + // When specified, it replaces the default preflight configuration for install/upgrade actions. + // When not specified, the default configuration will be used. + // + // +optional + Preflight *PreflightConfig `json:"preflight,omitempty"` +} + +// ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into +// the ClusterExtension's rendered manifests. +// +// +kubebuilder:validation:XValidation:rule="has(self.configType) && self.configType == 'Inline' ?has(self.inline) : !has(self.inline)",message="inline is required when configType is Inline, and forbidden otherwise" +// +union +type ClusterExtensionConfig struct { + // configType is a required reference to the type of configuration source. + // + // Allowed values are "Inline" + // + // When this field is set to "Inline", the cluster extension configuration is defined inline within the + // ClusterExtension resource. + // + // +unionDiscriminator + // +kubebuilder:validation:Enum:="Inline" + // +kubebuilder:validation:Required + ConfigType ClusterExtensionConfigType `json:"configType"` + + // inline contains JSON or YAML values specified directly in the + // ClusterExtension. + // + // inline must be set if configType is 'Inline'. + // inline accepts arbitrary JSON/YAML objects. + // inline is validation at runtime against the schema provided by the bundle if a schema is provided. + // + // +kubebuilder:validation:Type=object + // +optional + Inline *apiextensionsv1.JSON `json:"inline,omitempty"` +} + +// CatalogFilter defines the attributes used to identify and filter content from a catalog. +type CatalogFilter struct { + // packageName is a reference to the name of the package to be installed + // and is used to filter the content from catalogs. + // + // packageName is required, immutable, and follows the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. + // + // Some examples of valid values are: + // - some-package + // - 123-package + // - 1-package-2 + // - somepackage + // + // Some examples of invalid values are: + // - -some-package + // - some-package- + // - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + // - some.package + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation.Required + // +kubebuilder:validation:MaxLength:=253 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="packageName is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" + // +kubebuilder:validation:Required + PackageName string `json:"packageName"` + + // version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + // + // Acceptable version ranges are no longer than 64 characters. + // Version ranges are composed of comma- or space-delimited values and one or + // more comparison operators, known as comparison strings. Additional + // comparison strings can be added using the OR operator (||). + // + // # Range Comparisons + // + // To specify a version range, you can use a comparison string like ">=3.0, + // <3.6". When specifying a range, automatic updates will occur within that + // range. The example comparison string means "install any version greater than + // or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + // upgrades are available within the version range after initial installation, + // those upgrades should be automatically performed. + // + // # Pinned Versions + // + // To specify an exact version to install you can use a version range that + // "pins" to a specific version. When pinning to a specific version, no + // automatic updates will occur. An example of a pinned version range is + // "0.6.0", which means "only install version 0.6.0 and never + // upgrade from this version". + // + // # Basic Comparison Operators + // + // The basic comparison operators and their meanings are: + // - "=", equal (not aliased to an operator) + // - "!=", not equal + // - "<", less than + // - ">", greater than + // - ">=", greater than OR equal to + // - "<=", less than OR equal to + // + // # Wildcard Comparisons + // + // You can use the "x", "X", and "*" characters as wildcard characters in all + // comparison operations. Some examples of using the wildcard characters: + // - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + // - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + // - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + // - "x", "X", and "*" is equivalent to ">= 0.0.0" + // + // # Patch Release Comparisons + // + // When you want to specify a minor version up to the next major version you + // can use the "~" character to perform patch comparisons. Some examples: + // - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + // - "~1" and "~1.x" is equivalent to ">=1, <2" + // - "~2.3" is equivalent to ">=2.3, <2.4" + // - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + // + // # Major Release Comparisons + // + // You can use the "^" character to make major release comparisons after a + // stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + // - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + // - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + // - "^2.3" is equivalent to ">=2.3, <3" + // - "^2.x" is equivalent to ">=2.0.0, <3" + // - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + // - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + // - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + // - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + // - "^0" is equivalent to ">=0.0.0, <1.0.0" + // + // # OR Comparisons + // You can use the "||" character to represent an OR operation in the version + // range. Some examples: + // - ">=1.2.3, <2.0.0 || >3.0.0" + // - "^0 || ^3 || ^5" + // + // For more information on semver, please see https://semver.org/ + // + // +kubebuilder:validation:MaxLength:=64 + // +kubebuilder:validation:XValidation:rule="self.matches(\"^(\\\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|[x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)((?:\\\\s+|,\\\\s*|\\\\s*\\\\|\\\\|\\\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)*$\")",message="invalid version expression" + // +optional + Version string `json:"version,omitempty"` + + // channels is an optional reference to a set of channels belonging to + // the package specified in the packageName field. + // + // A "channel" is a package-author-defined stream of updates for an extension. + // + // Each channel in the list must follow the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. No more than 256 channels can be specified. + // + // When specified, it is used to constrain the set of installable bundles and + // the automated upgrade path. This constraint is an AND operation with the + // version field. For example: + // - Given channel is set to "foo" + // - Given version is set to ">=1.0.0, <1.5.0" + // - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + // - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + // + // When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + // + // Some examples of valid values are: + // - 1.1.x + // - alpha + // - stable + // - stable-v1 + // - v1-stable + // - dev-preview + // - preview + // - community + // + // Some examples of invalid values are: + // - -some-channel + // - some-channel- + // - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + // - original_40 + // - --default-channel + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation:items:MaxLength:=253 + // +kubebuilder:validation:MaxItems:=256 + // +kubebuilder:validation:items:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="channels entries must be valid DNS1123 subdomains" + // +optional + Channels []string `json:"channels,omitempty"` + + // selector is an optional field that can be used + // to filter the set of ClusterCatalogs used in the bundle + // selection process. + // + // When unspecified, all ClusterCatalogs will be used in + // the bundle selection process. + // + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty"` + + // upgradeConstraintPolicy is an optional field that controls whether + // the upgrade path(s) defined in the catalog are enforced for the package + // referenced in the packageName field. + // + // Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + // + // When this field is set to "CatalogProvided", automatic upgrades will only occur + // when upgrade constraints specified by the package author are met. + // + // When this field is set to "SelfCertified", the upgrade constraints specified by + // the package author are ignored. This allows for upgrades and downgrades to + // any version of the package. This is considered a dangerous operation as it + // can lead to unknown and potentially disastrous outcomes, such as data + // loss. It is assumed that users have independently verified changes when + // using this option. + // + // When this field is omitted, the default value is "CatalogProvided". + // + // +kubebuilder:validation:Enum:=CatalogProvided;SelfCertified + // +kubebuilder:default:=CatalogProvided + // +optional + UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"` +} + +// ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension. +type ServiceAccountReference struct { + // name is a required, immutable reference to the name of the ServiceAccount + // to be used for installation and management of the content for the package + // specified in the packageName field. + // + // This ServiceAccount must exist in the installNamespace. + // + // name follows the DNS subdomain standard as defined in [RFC 1123]. + // It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. + // + // Some examples of valid values are: + // - some-serviceaccount + // - 123-serviceaccount + // - 1-serviceaccount-2 + // - someserviceaccount + // - some.serviceaccount + // + // Some examples of invalid values are: + // - -some-serviceaccount + // - some-serviceaccount- + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation:MaxLength:=253 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="name must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" + // +kubebuilder:validation:Required + Name string `json:"name"` +} + +// PreflightConfig holds the configuration for the preflight checks. If used, at least one preflight check must be non-nil. +// +// +kubebuilder:validation:XValidation:rule="has(self.crdUpgradeSafety)",message="at least one of [crdUpgradeSafety] are required when preflight is specified" +type PreflightConfig struct { + // crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + // checks that run prior to upgrades of installed content. + // + // The CRD Upgrade Safety pre-flight check safeguards from unintended + // consequences of upgrading a CRD, such as data loss. + CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety"` +} + +// CRDUpgradeSafetyPreflightConfig is the configuration for CRD upgrade safety preflight check. +type CRDUpgradeSafetyPreflightConfig struct { + // enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + // + // Allowed values are "None" or "Strict". The default value is "Strict". + // + // When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + // when performing an upgrade operation. This should be used with caution as + // unintended consequences such as data loss can occur. + // + // When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + // performing an upgrade operation. + // + // +kubebuilder:validation:Enum:="None";"Strict" + // +kubebuilder:validation:Required + Enforcement CRDUpgradeSafetyEnforcement `json:"enforcement"` +} + +const ( + // TypeDeprecated is a rollup condition that is present when + // any of the deprecated conditions are present. + TypeDeprecated = "Deprecated" + TypePackageDeprecated = "PackageDeprecated" + TypeChannelDeprecated = "ChannelDeprecated" + TypeBundleDeprecated = "BundleDeprecated" + + // None will not perform CRD upgrade safety checks. + CRDUpgradeSafetyEnforcementNone CRDUpgradeSafetyEnforcement = "None" + // Strict will enforce the CRD upgrade safety check and block the upgrade if the CRD would not pass the check. + CRDUpgradeSafetyEnforcementStrict CRDUpgradeSafetyEnforcement = "Strict" +) + +// BundleMetadata is a representation of the identifying attributes of a bundle. +type BundleMetadata struct { + // name is required and follows the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" + Name string `json:"name"` + + // version is a required field and is a reference to the version that this bundle represents + // version follows the semantic versioning standard as defined in https://semver.org/. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self.matches(\"^([0-9]+)(\\\\.[0-9]+)?(\\\\.[0-9]+)?(-([-0-9A-Za-z]+(\\\\.[-0-9A-Za-z]+)*))?(\\\\+([-0-9A-Za-z]+(-\\\\.[-0-9A-Za-z]+)*))?\")",message="version must be well-formed semver" + Version string `json:"version"` +} + +// ClusterExtensionStatus defines the observed state of a ClusterExtension. +type ClusterExtensionStatus struct { + // The set of condition types which apply to all spec.source variations are Installed and Progressing. + // + // The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + // When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + // When Installed is False and the Reason is Failed, the bundle has failed to install. + // + // The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + // When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + // When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + // When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + // + // When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + // These are indications from a package owner to guide users away from a particular package, channel, or bundle. + // BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + // ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + // PackageDeprecated is set if the requested package is marked deprecated in the catalog. + // Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + // + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // install is a representation of the current installation status for this ClusterExtension. + // + // +optional + Install *ClusterExtensionInstallStatus `json:"install,omitempty"` +} + +// ClusterExtensionInstallStatus is a representation of the status of the identified bundle. +type ClusterExtensionInstallStatus struct { + // bundle is a required field which represents the identifying attributes of a bundle. + // + // A "bundle" is a versioned set of content that represents the resources that + // need to be applied to a cluster to install a package. + // + // +kubebuilder:validation:Required + Bundle BundleMetadata `json:"bundle"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Installed Bundle",type=string,JSONPath=`.status.install.bundle.name` +// +kubebuilder:printcolumn:name=Version,type=string,JSONPath=`.status.install.bundle.version` +// +kubebuilder:printcolumn:name="Installed",type=string,JSONPath=`.status.conditions[?(@.type=='Installed')].status` +// +kubebuilder:printcolumn:name="Progressing",type=string,JSONPath=`.status.conditions[?(@.type=='Progressing')].status` +// +kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp` + +// ClusterExtension is the Schema for the clusterextensions API +type ClusterExtension struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is an optional field that defines the desired state of the ClusterExtension. + // +optional + Spec ClusterExtensionSpec `json:"spec,omitempty"` + + // status is an optional field that defines the observed state of the ClusterExtension. + // +optional + Status ClusterExtensionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ClusterExtensionList contains a list of ClusterExtension +type ClusterExtensionList struct { + metav1.TypeMeta `json:",inline"` + + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items is a required list of ClusterExtension objects. + // + // +kubebuilder:validation:Required + Items []ClusterExtension `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ClusterExtension{}, &ClusterExtensionList{}) +} diff --git a/api/v1/clusterextension_types_test.go b/api/v1/clusterextension_types_test.go new file mode 100644 index 0000000000..feb9b4e224 --- /dev/null +++ b/api/v1/clusterextension_types_test.go @@ -0,0 +1,102 @@ +package v1_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "strconv" + "strings" + "testing" + + "golang.org/x/exp/slices" // replace with "slices" in go 1.21 + + "github.com/operator-framework/operator-controller/internal/operator-controller/conditionsets" +) + +// TODO Expand these tests to cover Types/Reasons/etc. from other APIs as well + +func TestClusterExtensionTypeRegistration(t *testing.T) { + types, err := parseConstants("Type") + if err != nil { + t.Fatalf("unable to parse Type constants %v", err) + } + + for _, tt := range types { + if !slices.Contains(conditionsets.ConditionTypes, tt) { + t.Errorf("append Type%s to conditionsets.ConditionTypes in this package's init function", tt) + } + } + + for _, tt := range conditionsets.ConditionTypes { + if !slices.Contains(types, tt) { + t.Errorf("there must be a Type%[1]s string literal constant for type %[1]q (i.e. 'const Type%[1]s = %[1]q')", tt) + } + } +} + +func TestClusterExtensionReasonRegistration(t *testing.T) { + reasons, err := parseConstants("Reason") + if err != nil { + t.Fatalf("unable to parse Reason constants %v", err) + } + + for _, r := range reasons { + if !slices.Contains(conditionsets.ConditionReasons, r) { + t.Errorf("append Reason%s to conditionsets.ConditionReasons in this package's init function.", r) + } + } + for _, r := range conditionsets.ConditionReasons { + if !slices.Contains(reasons, r) { + t.Errorf("there must be a Reason%[1]s string literal constant for reason %[1]q (i.e. 'const Reason%[1]s = %[1]q')", r) + } + } +} + +// parseConstants parses the values of the top-level constants that start with the given prefix, +// in the files clusterextension_types.go and common_types.go. +func parseConstants(prefix string) ([]string, error) { + fset := token.NewFileSet() + // An AST is a representation of the source code that can be traversed to extract information. + // Converting files to AST representation to extract information. + parseFiles, astFiles := []string{"clusterextension_types.go", "common_types.go"}, []*ast.File{} + for _, file := range parseFiles { + p, err := parser.ParseFile(fset, file, nil, 0) + if err != nil { + return nil, err + } + astFiles = append(astFiles, p) + } + + var constValues []string + + // Iterate all of the top-level declarations in each file, looking + // for constants that start with the prefix. When we find one, add + // its value to the constValues list. + for _, f := range astFiles { + for _, d := range f.Decls { + genDecl, ok := d.(*ast.GenDecl) + if !ok { + continue + } + for _, s := range genDecl.Specs { + valueSpec, ok := s.(*ast.ValueSpec) + if !ok || len(valueSpec.Names) != 1 || valueSpec.Names[0].Obj.Kind != ast.Con || !strings.HasPrefix(valueSpec.Names[0].String(), prefix) { + continue + } + for _, val := range valueSpec.Values { + lit, ok := val.(*ast.BasicLit) + if !ok || lit.Kind != token.STRING { + continue + } + v, err := strconv.Unquote(lit.Value) + if err != nil { + return nil, fmt.Errorf("unquote literal string %s: %v", lit.Value, err) + } + constValues = append(constValues, v) + } + } + } + } + return constValues, nil +} diff --git a/api/v1/clusterextensionrevision_types.go b/api/v1/clusterextensionrevision_types.go new file mode 100644 index 0000000000..13ac4ce2a7 --- /dev/null +++ b/api/v1/clusterextensionrevision_types.go @@ -0,0 +1,184 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" +) + +const ( + ClusterExtensionRevisionKind = "ClusterExtensionRevision" + + // Condition Types + ClusterExtensionRevisionTypeAvailable = "Available" + ClusterExtensionRevisionTypeSucceeded = "Succeeded" + + // Condition Reasons + ClusterExtensionRevisionReasonAvailable = "Available" + ClusterExtensionRevisionReasonReconcileFailure = "ReconcileFailure" + ClusterExtensionRevisionReasonRevisionValidationFailure = "RevisionValidationFailure" + ClusterExtensionRevisionReasonPhaseValidationError = "PhaseValidationError" + ClusterExtensionRevisionReasonObjectCollisions = "ObjectCollisions" + ClusterExtensionRevisionReasonRolloutSuccess = "RolloutSuccess" + ClusterExtensionRevisionReasonProbeFailure = "ProbeFailure" + ClusterExtensionRevisionReasonIncomplete = "Incomplete" + ClusterExtensionRevisionReasonProgressing = "Progressing" + ClusterExtensionRevisionReasonArchived = "Archived" +) + +// ClusterExtensionRevisionSpec defines the desired state of ClusterExtensionRevision. +type ClusterExtensionRevisionSpec struct { + // Specifies the lifecycle state of the ClusterExtensionRevision. + // + // +kubebuilder:default="Active" + // +kubebuilder:validation:Enum=Active;Paused;Archived + // +kubebuilder:validation:XValidation:rule="oldSelf == 'Active' || oldSelf == 'Paused' || oldSelf == 'Archived' && oldSelf == self", message="can not un-archive" + LifecycleState ClusterExtensionRevisionLifecycleState `json:"lifecycleState,omitempty"` + // Revision is a sequence number representing a specific revision of the ClusterExtension instance. + // Must be positive. Each ClusterExtensionRevision of the same parent ClusterExtension needs to have + // a unique value assigned. It is immutable after creation. The new revision number must always be previous revision +1. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:Minimum:=1 + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="revision is immutable" + Revision int64 `json:"revision"` + // Phases are groups of objects that will be applied at the same time. + // All objects in the phase will have to pass their probes in order to progress to the next phase. + // + // +kubebuilder:validation:XValidation:rule="self == oldSelf || oldSelf.size() == 0", message="phases is immutable" + // +listType=map + // +listMapKey=name + // +optional + Phases []ClusterExtensionRevisionPhase `json:"phases,omitempty"` + // Previous references previous revisions that objects can be adopted from. + // + // +kubebuilder:validation:XValidation:rule="self == oldSelf", message="previous is immutable" + Previous []ClusterExtensionRevisionPrevious `json:"previous,omitempty"` +} + +// ClusterExtensionRevisionLifecycleState specifies the lifecycle state of the ClusterExtensionRevision. +type ClusterExtensionRevisionLifecycleState string + +const ( + // ClusterExtensionRevisionLifecycleStateActive / "Active" is the default lifecycle state. + ClusterExtensionRevisionLifecycleStateActive ClusterExtensionRevisionLifecycleState = "Active" + // ClusterExtensionRevisionLifecycleStatePaused / "Paused" disables reconciliation of the ClusterExtensionRevision. + // Only Status updates will still propagated, but object changes will not be reconciled. + ClusterExtensionRevisionLifecycleStatePaused ClusterExtensionRevisionLifecycleState = "Paused" + // ClusterExtensionRevisionLifecycleStateArchived / "Archived" disables reconciliation while also "scaling to zero", + // which deletes all objects that are not excluded via the pausedFor property and + // removes itself from the owner list of all other objects previously under management. + ClusterExtensionRevisionLifecycleStateArchived ClusterExtensionRevisionLifecycleState = "Archived" +) + +// ClusterExtensionRevisionPhase are groups of objects that will be applied at the same time. +// All objects in the a phase will have to pass their probes in order to progress to the next phase. +type ClusterExtensionRevisionPhase struct { + // Name identifies this phase. + // + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Pattern=`^[a-z]([-a-z0-9]*[a-z0-9])?$` + Name string `json:"name"` + // Objects are a list of all the objects within this phase. + Objects []ClusterExtensionRevisionObject `json:"objects"` +} + +// ClusterExtensionRevisionObject contains an object and settings for it. +type ClusterExtensionRevisionObject struct { + // +kubebuilder:validation:EmbeddedResource + // +kubebuilder:pruning:PreserveUnknownFields + Object unstructured.Unstructured `json:"object"` + // CollisionProtection controls whether OLM can adopt and modify objects + // already existing on the cluster or even owned by another controller. + // + // +kubebuilder:default="Prevent" + // +optional + CollisionProtection CollisionProtection `json:"collisionProtection,omitempty"` +} + +// CollisionProtection specifies if and how ownership collisions are prevented. +type CollisionProtection string + +const ( + // CollisionProtectionPrevent prevents owner collisions entirely + // by only allowing to work with objects itself has created. + CollisionProtectionPrevent CollisionProtection = "Prevent" + // CollisionProtectionIfNoController allows to patch and override + // objects already present if they are not owned by another controller. + CollisionProtectionIfNoController CollisionProtection = "IfNoController" + // CollisionProtectionNone allows to patch and override objects + // already present and owned by other controllers. + // Be careful! This setting may cause multiple controllers to fight over a resource, + // causing load on the API server and etcd. + CollisionProtectionNone CollisionProtection = "None" +) + +type ClusterExtensionRevisionPrevious struct { + // +kubebuilder:validation:Required + Name string `json:"name"` + // +kubebuilder:validation:Required + UID types.UID `json:"uid"` +} + +// ClusterExtensionRevisionStatus defines the observed state of a ClusterExtensionRevision. +type ClusterExtensionRevisionStatus struct { + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status + +// ClusterExtensionRevision is the Schema for the clusterextensionrevisions API +// +kubebuilder:printcolumn:name="Available",type=string,JSONPath=`.status.conditions[?(@.type=='Available')].status` +// +kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp` +type ClusterExtensionRevision struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is an optional field that defines the desired state of the ClusterExtension. + // +optional + Spec ClusterExtensionRevisionSpec `json:"spec,omitempty"` + + // status is an optional field that defines the observed state of the ClusterExtension. + // +optional + Status ClusterExtensionRevisionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ClusterExtensionRevisionList contains a list of ClusterExtensionRevision +type ClusterExtensionRevisionList struct { + metav1.TypeMeta `json:",inline"` + + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items is a required list of ClusterExtensionRevision objects. + // + // +kubebuilder:validation:Required + Items []ClusterExtensionRevision `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ClusterExtensionRevision{}, &ClusterExtensionRevisionList{}) +} diff --git a/api/v1/clusterextensionrevision_types_test.go b/api/v1/clusterextensionrevision_types_test.go new file mode 100644 index 0000000000..9792826fb8 --- /dev/null +++ b/api/v1/clusterextensionrevision_types_test.go @@ -0,0 +1,142 @@ +package v1 + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestClusterExtensionRevisionImmutability(t *testing.T) { + c := newClient(t) + ctx := context.Background() + i := 0 + for name, tc := range map[string]struct { + spec ClusterExtensionRevisionSpec + updateFunc func(*ClusterExtensionRevision) + allowed bool + }{ + "revision is immutable": { + spec: ClusterExtensionRevisionSpec{ + Revision: 1, + }, + updateFunc: func(cer *ClusterExtensionRevision) { + cer.Spec.Revision = 2 + }, + }, + "phases may be initially empty": { + spec: ClusterExtensionRevisionSpec{ + Revision: 1, + Phases: []ClusterExtensionRevisionPhase{}, + }, + updateFunc: func(cer *ClusterExtensionRevision) { + cer.Spec.Phases = []ClusterExtensionRevisionPhase{ + { + Name: "foo", + Objects: []ClusterExtensionRevisionObject{}, + }, + } + }, + allowed: true, + }, + "phases may be initially unset": { + spec: ClusterExtensionRevisionSpec{ + Revision: 1, + }, + updateFunc: func(cer *ClusterExtensionRevision) { + cer.Spec.Phases = []ClusterExtensionRevisionPhase{ + { + Name: "foo", + Objects: []ClusterExtensionRevisionObject{}, + }, + } + }, + allowed: true, + }, + "phases are immutable if not empty": { + spec: ClusterExtensionRevisionSpec{ + Revision: 1, + Phases: []ClusterExtensionRevisionPhase{ + { + Name: "foo", + Objects: []ClusterExtensionRevisionObject{}, + }, + }, + }, + updateFunc: func(cer *ClusterExtensionRevision) { + cer.Spec.Phases = []ClusterExtensionRevisionPhase{ + { + Name: "foo2", + Objects: []ClusterExtensionRevisionObject{}, + }, + } + }, + }, + } { + t.Run(name, func(t *testing.T) { + cer := &ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("foo%d", i), + }, + Spec: tc.spec, + } + i = i + 1 + require.NoError(t, c.Create(ctx, cer)) + tc.updateFunc(cer) + err := c.Update(ctx, cer) + if tc.allowed && err != nil { + t.Fatal("expected update to succeed, but got:", err) + } + if !tc.allowed && !errors.IsInvalid(err) { + t.Fatal("expected update to fail due to invalid payload, but got:", err) + } + }) + } +} + +func TestClusterExtensionRevisionValidity(t *testing.T) { + c := newClient(t) + ctx := context.Background() + i := 0 + for name, tc := range map[string]struct { + spec ClusterExtensionRevisionSpec + valid bool + }{ + "revision cannot be negative": { + spec: ClusterExtensionRevisionSpec{ + Revision: -1, + }, + valid: false, + }, + "revision cannot be zero": { + spec: ClusterExtensionRevisionSpec{}, + valid: false, + }, + "revision must be positive": { + spec: ClusterExtensionRevisionSpec{ + Revision: 1, + }, + valid: true, + }, + } { + t.Run(name, func(t *testing.T) { + cer := &ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("bar%d", i), + }, + Spec: tc.spec, + } + i = i + 1 + err := c.Create(ctx, cer) + if tc.valid && err != nil { + t.Fatal("expected create to succeed, but got:", err) + } + if !tc.valid && !errors.IsInvalid(err) { + t.Fatal("expected create to fail due to invalid payload, but got:", err) + } + }) + } +} diff --git a/api/v1/common_types.go b/api/v1/common_types.go new file mode 100644 index 0000000000..6ab5336ac2 --- /dev/null +++ b/api/v1/common_types.go @@ -0,0 +1,37 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +const ( + TypeInstalled = "Installed" + TypeProgressing = "Progressing" + + // Installed reasons + ReasonAbsent = "Absent" + + // Progressing reasons + ReasonRolloutInProgress = "RolloutInProgress" + ReasonRetrying = "Retrying" + ReasonBlocked = "Blocked" + + // Deprecation reasons + ReasonDeprecated = "Deprecated" + + // Common reasons + ReasonSucceeded = "Succeeded" + ReasonFailed = "Failed" +) diff --git a/api/v1alpha1/groupversion_info.go b/api/v1/groupversion_info.go similarity index 89% rename from api/v1alpha1/groupversion_info.go rename to api/v1/groupversion_info.go index f46abbf3da..f2e8582ee5 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1/groupversion_info.go @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1alpha1 contains API Schema definitions for the olm v1alpha1 API group +// Package v1 contains API Schema definitions for the olm v1 API group // +kubebuilder:object:generate=true // +groupName=olm.operatorframework.io -package v1alpha1 +package v1 import ( "k8s.io/apimachinery/pkg/runtime/schema" @@ -26,7 +26,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "olm.operatorframework.io", Version: "v1alpha1"} + GroupVersion = schema.GroupVersion{Group: "olm.operatorframework.io", Version: "v1"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/api/v1/suite_test.go b/api/v1/suite_test.go new file mode 100644 index 0000000000..bc7a0c22bc --- /dev/null +++ b/api/v1/suite_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "log" + "os" + "testing" + + "github.com/stretchr/testify/require" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/test" +) + +func newScheme(t *testing.T) *apimachineryruntime.Scheme { + sch := apimachineryruntime.NewScheme() + require.NoError(t, AddToScheme(sch)) + return sch +} + +func newClient(t *testing.T) client.Client { + cl, err := client.New(config, client.Options{Scheme: newScheme(t)}) + require.NoError(t, err) + require.NotNil(t, cl) + return cl +} + +var config *rest.Config + +func TestMain(m *testing.M) { + testEnv := test.NewEnv() + + var err error + config, err = testEnv.Start() + utilruntime.Must(err) + if config == nil { + log.Panic("expected cfg to not be nil") + } + + code := m.Run() + utilruntime.Must(testEnv.Stop()) + os.Exit(code) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..e13f1532b0 --- /dev/null +++ b/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,665 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BundleMetadata) DeepCopyInto(out *BundleMetadata) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleMetadata. +func (in *BundleMetadata) DeepCopy() *BundleMetadata { + if in == nil { + return nil + } + out := new(BundleMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CRDUpgradeSafetyPreflightConfig) DeepCopyInto(out *CRDUpgradeSafetyPreflightConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CRDUpgradeSafetyPreflightConfig. +func (in *CRDUpgradeSafetyPreflightConfig) DeepCopy() *CRDUpgradeSafetyPreflightConfig { + if in == nil { + return nil + } + out := new(CRDUpgradeSafetyPreflightConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogFilter) DeepCopyInto(out *CatalogFilter) { + *out = *in + if in.Channels != nil { + in, out := &in.Channels, &out.Channels + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogFilter. +func (in *CatalogFilter) DeepCopy() *CatalogFilter { + if in == nil { + return nil + } + out := new(CatalogFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogSource) DeepCopyInto(out *CatalogSource) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageSource) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSource. +func (in *CatalogSource) DeepCopy() *CatalogSource { + if in == nil { + return nil + } + out := new(CatalogSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCatalog) DeepCopyInto(out *ClusterCatalog) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalog. +func (in *ClusterCatalog) DeepCopy() *ClusterCatalog { + if in == nil { + return nil + } + out := new(ClusterCatalog) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterCatalog) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCatalogList) DeepCopyInto(out *ClusterCatalogList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterCatalog, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogList. +func (in *ClusterCatalogList) DeepCopy() *ClusterCatalogList { + if in == nil { + return nil + } + out := new(ClusterCatalogList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterCatalogList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCatalogSpec) DeepCopyInto(out *ClusterCatalogSpec) { + *out = *in + in.Source.DeepCopyInto(&out.Source) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogSpec. +func (in *ClusterCatalogSpec) DeepCopy() *ClusterCatalogSpec { + if in == nil { + return nil + } + out := new(ClusterCatalogSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCatalogStatus) DeepCopyInto(out *ClusterCatalogStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ResolvedSource != nil { + in, out := &in.ResolvedSource, &out.ResolvedSource + *out = new(ResolvedCatalogSource) + (*in).DeepCopyInto(*out) + } + if in.URLs != nil { + in, out := &in.URLs, &out.URLs + *out = new(ClusterCatalogURLs) + **out = **in + } + if in.LastUnpacked != nil { + in, out := &in.LastUnpacked, &out.LastUnpacked + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogStatus. +func (in *ClusterCatalogStatus) DeepCopy() *ClusterCatalogStatus { + if in == nil { + return nil + } + out := new(ClusterCatalogStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCatalogURLs) DeepCopyInto(out *ClusterCatalogURLs) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCatalogURLs. +func (in *ClusterCatalogURLs) DeepCopy() *ClusterCatalogURLs { + if in == nil { + return nil + } + out := new(ClusterCatalogURLs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtension) DeepCopyInto(out *ClusterExtension) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtension. +func (in *ClusterExtension) DeepCopy() *ClusterExtension { + if in == nil { + return nil + } + out := new(ClusterExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtension) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionConfig) DeepCopyInto(out *ClusterExtensionConfig) { + *out = *in + if in.Inline != nil { + in, out := &in.Inline, &out.Inline + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionConfig. +func (in *ClusterExtensionConfig) DeepCopy() *ClusterExtensionConfig { + if in == nil { + return nil + } + out := new(ClusterExtensionConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionInstallConfig) DeepCopyInto(out *ClusterExtensionInstallConfig) { + *out = *in + if in.Preflight != nil { + in, out := &in.Preflight, &out.Preflight + *out = new(PreflightConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionInstallConfig. +func (in *ClusterExtensionInstallConfig) DeepCopy() *ClusterExtensionInstallConfig { + if in == nil { + return nil + } + out := new(ClusterExtensionInstallConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionInstallStatus) DeepCopyInto(out *ClusterExtensionInstallStatus) { + *out = *in + out.Bundle = in.Bundle +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionInstallStatus. +func (in *ClusterExtensionInstallStatus) DeepCopy() *ClusterExtensionInstallStatus { + if in == nil { + return nil + } + out := new(ClusterExtensionInstallStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionList) DeepCopyInto(out *ClusterExtensionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionList. +func (in *ClusterExtensionList) DeepCopy() *ClusterExtensionList { + if in == nil { + return nil + } + out := new(ClusterExtensionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtensionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevision) DeepCopyInto(out *ClusterExtensionRevision) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevision. +func (in *ClusterExtensionRevision) DeepCopy() *ClusterExtensionRevision { + if in == nil { + return nil + } + out := new(ClusterExtensionRevision) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtensionRevision) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionList) DeepCopyInto(out *ClusterExtensionRevisionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterExtensionRevision, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionList. +func (in *ClusterExtensionRevisionList) DeepCopy() *ClusterExtensionRevisionList { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtensionRevisionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionObject) DeepCopyInto(out *ClusterExtensionRevisionObject) { + *out = *in + in.Object.DeepCopyInto(&out.Object) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionObject. +func (in *ClusterExtensionRevisionObject) DeepCopy() *ClusterExtensionRevisionObject { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionPhase) DeepCopyInto(out *ClusterExtensionRevisionPhase) { + *out = *in + if in.Objects != nil { + in, out := &in.Objects, &out.Objects + *out = make([]ClusterExtensionRevisionObject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionPhase. +func (in *ClusterExtensionRevisionPhase) DeepCopy() *ClusterExtensionRevisionPhase { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionPhase) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionPrevious) DeepCopyInto(out *ClusterExtensionRevisionPrevious) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionPrevious. +func (in *ClusterExtensionRevisionPrevious) DeepCopy() *ClusterExtensionRevisionPrevious { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionPrevious) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionSpec) DeepCopyInto(out *ClusterExtensionRevisionSpec) { + *out = *in + if in.Phases != nil { + in, out := &in.Phases, &out.Phases + *out = make([]ClusterExtensionRevisionPhase, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Previous != nil { + in, out := &in.Previous, &out.Previous + *out = make([]ClusterExtensionRevisionPrevious, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionSpec. +func (in *ClusterExtensionRevisionSpec) DeepCopy() *ClusterExtensionRevisionSpec { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionRevisionStatus) DeepCopyInto(out *ClusterExtensionRevisionStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionRevisionStatus. +func (in *ClusterExtensionRevisionStatus) DeepCopy() *ClusterExtensionRevisionStatus { + if in == nil { + return nil + } + out := new(ClusterExtensionRevisionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionSpec) DeepCopyInto(out *ClusterExtensionSpec) { + *out = *in + out.ServiceAccount = in.ServiceAccount + in.Source.DeepCopyInto(&out.Source) + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(ClusterExtensionInstallConfig) + (*in).DeepCopyInto(*out) + } + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(ClusterExtensionConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. +func (in *ClusterExtensionSpec) DeepCopy() *ClusterExtensionSpec { + if in == nil { + return nil + } + out := new(ClusterExtensionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionStatus) DeepCopyInto(out *ClusterExtensionStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(ClusterExtensionInstallStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionStatus. +func (in *ClusterExtensionStatus) DeepCopy() *ClusterExtensionStatus { + if in == nil { + return nil + } + out := new(ClusterExtensionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageSource) DeepCopyInto(out *ImageSource) { + *out = *in + if in.PollIntervalMinutes != nil { + in, out := &in.PollIntervalMinutes, &out.PollIntervalMinutes + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSource. +func (in *ImageSource) DeepCopy() *ImageSource { + if in == nil { + return nil + } + out := new(ImageSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PreflightConfig) DeepCopyInto(out *PreflightConfig) { + *out = *in + if in.CRDUpgradeSafety != nil { + in, out := &in.CRDUpgradeSafety, &out.CRDUpgradeSafety + *out = new(CRDUpgradeSafetyPreflightConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightConfig. +func (in *PreflightConfig) DeepCopy() *PreflightConfig { + if in == nil { + return nil + } + out := new(PreflightConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResolvedCatalogSource) DeepCopyInto(out *ResolvedCatalogSource) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ResolvedImageSource) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedCatalogSource. +func (in *ResolvedCatalogSource) DeepCopy() *ResolvedCatalogSource { + if in == nil { + return nil + } + out := new(ResolvedCatalogSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResolvedImageSource) DeepCopyInto(out *ResolvedImageSource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResolvedImageSource. +func (in *ResolvedImageSource) DeepCopy() *ResolvedImageSource { + if in == nil { + return nil + } + out := new(ResolvedImageSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountReference) DeepCopyInto(out *ServiceAccountReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountReference. +func (in *ServiceAccountReference) DeepCopy() *ServiceAccountReference { + if in == nil { + return nil + } + out := new(ServiceAccountReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceConfig) DeepCopyInto(out *SourceConfig) { + *out = *in + if in.Catalog != nil { + in, out := &in.Catalog, &out.Catalog + *out = new(CatalogFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceConfig. +func (in *SourceConfig) DeepCopy() *SourceConfig { + if in == nil { + return nil + } + out := new(SourceConfig) + in.DeepCopyInto(out) + return out +} diff --git a/api/v1alpha1/clusterextension_types_test.go b/api/v1alpha1/clusterextension_types_test.go deleted file mode 100644 index 0ed4f1a088..0000000000 --- a/api/v1alpha1/clusterextension_types_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package v1alpha1_test - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/fs" - "strconv" - "strings" - "testing" - - "golang.org/x/exp/slices" // replace with "slices" in go 1.21 - - "github.com/operator-framework/operator-controller/internal/conditionsets" -) - -func TestClusterExtensionTypeRegistration(t *testing.T) { - types, err := parseConstants("Type") - if err != nil { - t.Fatalf("unable to parse Type constants %v", err) - } - - for _, tt := range types { - if !slices.Contains(conditionsets.ConditionTypes, tt) { - t.Errorf("append Type%s to conditionsets.ConditionTypes in this package's init function", tt) - } - } - - for _, tt := range conditionsets.ConditionTypes { - if !slices.Contains(types, tt) { - t.Errorf("there must be a Type%[1]s string literal constant for type %[1]q (i.e. 'const Type%[1]s = %[1]q')", tt) - } - } -} - -func TestClusterExtensionReasonRegistration(t *testing.T) { - reasons, err := parseConstants("Reason") - if err != nil { - t.Fatalf("unable to parse Reason constants %v", err) - } - - for _, r := range reasons { - if !slices.Contains(conditionsets.ConditionReasons, r) { - t.Errorf("append Reason%s to conditionsets.ConditionReasons in this package's init function.", r) - } - } - for _, r := range conditionsets.ConditionReasons { - if !slices.Contains(reasons, r) { - t.Errorf("there must be a Reason%[1]s string literal constant for reason %[1]q (i.e. 'const Reason%[1]s = %[1]q')", r) - } - } -} - -// parseConstants parses the values of the top-level constants in the current -// directory whose names start with the given prefix. When running as part of a -// test, the current directory is the directory of the file that contains the -// test in which this function is called. -func parseConstants(prefix string) ([]string, error) { - fset := token.NewFileSet() - // ParseDir returns a map of package name to package ASTs. An AST is a representation of the source code - // that can be traversed to extract information. The map is keyed by the package name. - pkgs, err := parser.ParseDir(fset, ".", func(info fs.FileInfo) bool { - return !strings.HasSuffix(info.Name(), "_test.go") - }, 0) - if err != nil { - return nil, err - } - - var constValues []string - - // Iterate all of the top-level declarations in each package's files, - // looking for constants that start with the prefix. When we find one, - // add its value to the constValues list. - for _, pkg := range pkgs { - for _, f := range pkg.Files { - for _, d := range f.Decls { - genDecl, ok := d.(*ast.GenDecl) - if !ok { - continue - } - for _, s := range genDecl.Specs { - valueSpec, ok := s.(*ast.ValueSpec) - if !ok || len(valueSpec.Names) != 1 || valueSpec.Names[0].Obj.Kind != ast.Con || !strings.HasPrefix(valueSpec.Names[0].String(), prefix) { - continue - } - for _, val := range valueSpec.Values { - lit, ok := val.(*ast.BasicLit) - if !ok || lit.Kind != token.STRING { - continue - } - v, err := strconv.Unquote(lit.Value) - if err != nil { - return nil, fmt.Errorf("unquote literal string %s: %v", lit.Value, err) - } - constValues = append(constValues, v) - } - } - } - } - } - return constValues, nil -} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index ed65d696b9..0000000000 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,293 +0,0 @@ -//go:build !ignore_autogenerated - -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by controller-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BundleMetadata) DeepCopyInto(out *BundleMetadata) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleMetadata. -func (in *BundleMetadata) DeepCopy() *BundleMetadata { - if in == nil { - return nil - } - out := new(BundleMetadata) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CRDUpgradeSafetyPreflightConfig) DeepCopyInto(out *CRDUpgradeSafetyPreflightConfig) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CRDUpgradeSafetyPreflightConfig. -func (in *CRDUpgradeSafetyPreflightConfig) DeepCopy() *CRDUpgradeSafetyPreflightConfig { - if in == nil { - return nil - } - out := new(CRDUpgradeSafetyPreflightConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CatalogSource) DeepCopyInto(out *CatalogSource) { - *out = *in - if in.Channels != nil { - in, out := &in.Channels, &out.Channels - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.Selector.DeepCopyInto(&out.Selector) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSource. -func (in *CatalogSource) DeepCopy() *CatalogSource { - if in == nil { - return nil - } - out := new(CatalogSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtension) DeepCopyInto(out *ClusterExtension) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtension. -func (in *ClusterExtension) DeepCopy() *ClusterExtension { - if in == nil { - return nil - } - out := new(ClusterExtension) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterExtension) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionInstallConfig) DeepCopyInto(out *ClusterExtensionInstallConfig) { - *out = *in - out.ServiceAccount = in.ServiceAccount - if in.Preflight != nil { - in, out := &in.Preflight, &out.Preflight - *out = new(PreflightConfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionInstallConfig. -func (in *ClusterExtensionInstallConfig) DeepCopy() *ClusterExtensionInstallConfig { - if in == nil { - return nil - } - out := new(ClusterExtensionInstallConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionInstallStatus) DeepCopyInto(out *ClusterExtensionInstallStatus) { - *out = *in - out.Bundle = in.Bundle -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionInstallStatus. -func (in *ClusterExtensionInstallStatus) DeepCopy() *ClusterExtensionInstallStatus { - if in == nil { - return nil - } - out := new(ClusterExtensionInstallStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionList) DeepCopyInto(out *ClusterExtensionList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterExtension, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionList. -func (in *ClusterExtensionList) DeepCopy() *ClusterExtensionList { - if in == nil { - return nil - } - out := new(ClusterExtensionList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterExtensionList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionResolutionStatus) DeepCopyInto(out *ClusterExtensionResolutionStatus) { - *out = *in - out.Bundle = in.Bundle -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionResolutionStatus. -func (in *ClusterExtensionResolutionStatus) DeepCopy() *ClusterExtensionResolutionStatus { - if in == nil { - return nil - } - out := new(ClusterExtensionResolutionStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionSpec) DeepCopyInto(out *ClusterExtensionSpec) { - *out = *in - in.Source.DeepCopyInto(&out.Source) - in.Install.DeepCopyInto(&out.Install) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. -func (in *ClusterExtensionSpec) DeepCopy() *ClusterExtensionSpec { - if in == nil { - return nil - } - out := new(ClusterExtensionSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterExtensionStatus) DeepCopyInto(out *ClusterExtensionStatus) { - *out = *in - if in.Install != nil { - in, out := &in.Install, &out.Install - *out = new(ClusterExtensionInstallStatus) - **out = **in - } - if in.Resolution != nil { - in, out := &in.Resolution, &out.Resolution - *out = new(ClusterExtensionResolutionStatus) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionStatus. -func (in *ClusterExtensionStatus) DeepCopy() *ClusterExtensionStatus { - if in == nil { - return nil - } - out := new(ClusterExtensionStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PreflightConfig) DeepCopyInto(out *PreflightConfig) { - *out = *in - if in.CRDUpgradeSafety != nil { - in, out := &in.CRDUpgradeSafety, &out.CRDUpgradeSafety - *out = new(CRDUpgradeSafetyPreflightConfig) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightConfig. -func (in *PreflightConfig) DeepCopy() *PreflightConfig { - if in == nil { - return nil - } - out := new(PreflightConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccountReference) DeepCopyInto(out *ServiceAccountReference) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountReference. -func (in *ServiceAccountReference) DeepCopy() *ServiceAccountReference { - if in == nil { - return nil - } - out := new(ServiceAccountReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceConfig) DeepCopyInto(out *SourceConfig) { - *out = *in - if in.Catalog != nil { - in, out := &in.Catalog, &out.Catalog - *out = new(CatalogSource) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceConfig. -func (in *SourceConfig) DeepCopy() *SourceConfig { - if in == nil { - return nil - } - out := new(SourceConfig) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/OWNERS b/cmd/OWNERS new file mode 100644 index 0000000000..740420d64f --- /dev/null +++ b/cmd/OWNERS @@ -0,0 +1,2 @@ +approvers: + - cmd-approvers diff --git a/cmd/catalogd/main.go b/cmd/catalogd/main.go new file mode 100644 index 0000000000..36f7b16752 --- /dev/null +++ b/cmd/catalogd/main.go @@ -0,0 +1,459 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "crypto/tls" + "errors" + "flag" + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + "go.podman.io/image/v5/types" + "k8s.io/apimachinery/pkg/runtime" + k8stypes "k8s.io/apimachinery/pkg/types" + apimachineryrand "k8s.io/apimachinery/pkg/util/rand" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/metadata" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + crcache "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/metrics" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + crwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + corecontrollers "github.com/operator-framework/operator-controller/internal/catalogd/controllers/core" + "github.com/operator-framework/operator-controller/internal/catalogd/features" + "github.com/operator-framework/operator-controller/internal/catalogd/garbagecollection" + catalogdmetrics "github.com/operator-framework/operator-controller/internal/catalogd/metrics" + "github.com/operator-framework/operator-controller/internal/catalogd/serverutil" + "github.com/operator-framework/operator-controller/internal/catalogd/storage" + "github.com/operator-framework/operator-controller/internal/catalogd/webhook" + sharedcontrollers "github.com/operator-framework/operator-controller/internal/shared/controllers" + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" + httputil "github.com/operator-framework/operator-controller/internal/shared/util/http" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" + "github.com/operator-framework/operator-controller/internal/shared/util/pullsecretcache" + sautil "github.com/operator-framework/operator-controller/internal/shared/util/sa" + "github.com/operator-framework/operator-controller/internal/shared/util/tlsprofiles" + "github.com/operator-framework/operator-controller/internal/shared/version" +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") + cfg = &config{} +) + +const ( + storageDir = "catalogs" + authFilePrefix = "catalogd-global-pull-secret" +) + +type config struct { + metricsAddr string + enableLeaderElection bool + probeAddr string + pprofAddr string + systemNamespace string + catalogServerAddr string + externalAddr string + cacheDir string + gcInterval time.Duration + certFile string + keyFile string + webhookPort int + pullCasDir string + globalPullSecret string + // Generated config + globalPullSecretKey *k8stypes.NamespacedName +} + +var catalogdCmd = &cobra.Command{ + Use: "catalogd", + Short: "Catalogd is a Kubernetes operator for managing operator catalogs", + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateConfig(cfg); err != nil { + return err + } + cmd.SilenceUsage = true + return run(ctrl.SetupSignalHandler()) + }, +} + +var versionCommand = &cobra.Command{ + Use: "version", + Short: "Print the version information", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("%#v\n", version.String()) + }, +} + +func init() { + // create flagset, the collection of flags for this command + flags := catalogdCmd.Flags() + flags.StringVar(&cfg.metricsAddr, "metrics-bind-address", "", "The address for the metrics endpoint. Requires tls-cert and tls-key. (Default: ':7443')") + flags.StringVar(&cfg.probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flags.StringVar(&cfg.pprofAddr, "pprof-bind-address", "0", "The address the pprof endpoint binds to. an empty string or 0 disables pprof") + flags.BoolVar(&cfg.enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager") + flags.StringVar(&cfg.systemNamespace, "system-namespace", "", "The namespace catalogd uses for internal state") + flags.StringVar(&cfg.catalogServerAddr, "catalogs-server-addr", ":8443", "The address where catalogs' content will be accessible") + flags.StringVar(&cfg.externalAddr, "external-address", "catalogd-service.olmv1-system.svc", "External address for http(s) server") + flags.StringVar(&cfg.cacheDir, "cache-dir", "/var/cache/", "Directory for file based caching") + flags.DurationVar(&cfg.gcInterval, "gc-interval", 12*time.Hour, "Garbage collection interval") + flags.StringVar(&cfg.certFile, "tls-cert", "", "Certificate file for TLS") + flags.StringVar(&cfg.keyFile, "tls-key", "", "Key file for TLS") + flags.IntVar(&cfg.webhookPort, "webhook-server-port", 9443, "Webhook server port") + flag.StringVar(&cfg.pullCasDir, "pull-cas-dir", "", "The directory of TLS certificate authoritiess to use for verifying HTTPS copullCasDirnnections to image registries.") + flags.StringVar(&cfg.globalPullSecret, "global-pull-secret", "", "Global pull secret (/)") + + // adds version subcommand + catalogdCmd.AddCommand(versionCommand) + + // Add other flags + klog.InitFlags(flag.CommandLine) + flags.AddGoFlagSet(flag.CommandLine) + features.CatalogdFeatureGate.AddFlag(flags) + tlsprofiles.AddFlags(flags) + + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(ocv1.AddToScheme(scheme)) + ctrl.SetLogger(klog.NewKlogr()) +} + +func main() { + if err := catalogdCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func validateConfig(cfg *config) error { + if (cfg.certFile != "" && cfg.keyFile == "") || (cfg.certFile == "" && cfg.keyFile != "") { + err := fmt.Errorf("tls-cert and tls-key flags must be used together") + setupLog.Error(err, "missing TLS configuration", + "certFile", cfg.certFile, "keyFile", cfg.keyFile) + return err + } + + if cfg.metricsAddr != "" && cfg.certFile == "" && cfg.keyFile == "" { + err := fmt.Errorf("metrics-bind-address requires tls-cert and tls-key flags") + setupLog.Error(err, "invalid metrics configuration", + "metricsAddr", cfg.metricsAddr, "certFile", cfg.certFile, "keyFile", cfg.keyFile) + return err + } + + if cfg.certFile != "" && cfg.keyFile != "" && cfg.metricsAddr == "" { + cfg.metricsAddr = ":7443" + } + + if cfg.globalPullSecret != "" { + secretParts := strings.Split(cfg.globalPullSecret, "/") + if len(secretParts) != 2 { + err := errors.New("value of global-pull-secret should be of the format /") + setupLog.Error(err, "incorrect number of components", + "globalPullSecret", cfg.globalPullSecret) + return err + } + cfg.globalPullSecretKey = &k8stypes.NamespacedName{Name: secretParts[1], Namespace: secretParts[0]} + } + + return nil +} + +func run(ctx context.Context) error { + // log startup message and feature gate status + setupLog.Info("starting up catalogd", "version info", version.String()) + features.LogFeatureGateStates(setupLog, features.CatalogdFeatureGate) + authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8))) + + protocol := "http://" + if cfg.certFile != "" && cfg.keyFile != "" { + protocol = "https://" + } + cfg.externalAddr = protocol + cfg.externalAddr + + cw, err := certwatcher.New(cfg.certFile, cfg.keyFile) + if err != nil { + setupLog.Error(err, "failed to initialize certificate watcher") + return err + } + + tlsOpts := func(config *tls.Config) { + config.GetCertificate = cw.GetCertificate + // Ensure HTTP/2 is disabled by default for webhooks and metrics. + // Disabling HTTP/2 mitigates vulnerabilities associated with: + // - HTTP/2 Stream Cancellation (GHSA-qppj-fm5r-hxr3) + // - HTTP/2 Rapid Reset (GHSA-4374-p667-p6c8) + // While CVE fixes exist, they remain insufficient; disabling HTTP/2 helps reduce risks. + // For details, see: https://github.com/kubernetes/kubernetes/issues/121197 + config.NextProtos = []string{"http/1.1"} + } + tlsProfile, err := tlsprofiles.GetTLSConfigFunc() + if err != nil { + setupLog.Error(err, "failed to get TLS profile") + return err + } + + // Create webhook server and configure TLS + webhookServer := crwebhook.NewServer(crwebhook.Options{ + Port: cfg.webhookPort, + TLSOpts: []func(*tls.Config){ + tlsOpts, + tlsProfile, + }, + }) + + metricsServerOptions := metricsserver.Options{} + if len(cfg.certFile) > 0 && len(cfg.keyFile) > 0 { + setupLog.Info("Starting metrics server with TLS enabled", "addr", cfg.metricsAddr, "tls-cert", cfg.certFile, "tls-key", cfg.keyFile) + + metricsServerOptions.BindAddress = cfg.metricsAddr + metricsServerOptions.SecureServing = true + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, tlsOpts, tlsProfile) + } else { + // Note that the metrics server is not serving if the BindAddress is set to "0". + // Therefore, the metrics server is disabled by default. It is only enabled + // if certFile and keyFile are provided. The intention is not allowing the metrics + // be served with the default self-signed certificate generated by controller-runtime. + metricsServerOptions.BindAddress = "0" + setupLog.Info("WARNING: Metrics Server is disabled. " + + "Metrics will not be served since the TLS certificate and key file are not provided.") + } + + cacheOptions := crcache.Options{ + ByObject: map[client.Object]crcache.ByObject{}, + } + + saKey, err := sautil.GetServiceAccount() + if err != nil { + setupLog.Error(err, "Failed to extract serviceaccount from JWT") + return err + } + setupLog.Info("Successfully extracted serviceaccount from JWT", "serviceaccount", + fmt.Sprintf("%s/%s", saKey.Namespace, saKey.Name)) + + err = pullsecretcache.SetupPullSecretCache(&cacheOptions, cfg.globalPullSecretKey, saKey) + if err != nil { + setupLog.Error(err, "Unable to setup pull-secret cache") + return err + } + + // Create manager + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + PprofBindAddress: cfg.pprofAddr, + HealthProbeBindAddress: cfg.probeAddr, + LeaderElection: cfg.enableLeaderElection, + LeaderElectionID: "catalogd-operator-lock", + LeaderElectionReleaseOnCancel: true, + // Recommended Leader Election values + // https://github.com/openshift/enhancements/blob/61581dcd985130357d6e4b0e72b87ee35394bf6e/CONVENTIONS.md#handling-kube-apiserver-disruption + LeaseDuration: ptr.To(137 * time.Second), + RenewDeadline: ptr.To(107 * time.Second), + RetryPeriod: ptr.To(26 * time.Second), + + WebhookServer: webhookServer, + Cache: cacheOptions, + }) + if err != nil { + setupLog.Error(err, "unable to create manager") + return err + } + + // Add the certificate watcher to the manager + err = mgr.Add(cw) + if err != nil { + setupLog.Error(err, "unable to add certificate watcher to manager") + return err + } + + // This watches the pullCasDir and the SSL_CERT_DIR, and SSL_CERT_FILE for changes + cpwPull, err := httputil.NewCertPoolWatcher(cfg.pullCasDir, ctrl.Log.WithName("pull-ca-pool")) + if err != nil { + setupLog.Error(err, "unable to create pull-ca-pool watcher") + return err + } + cpwPull.Restart(os.Exit) + if err = mgr.Add(cpwPull); err != nil { + setupLog.Error(err, "unable to add pull-ca-pool watcher to manager") + return err + } + + if cfg.systemNamespace == "" { + cfg.systemNamespace = podNamespace() + } + + if err := fsutil.EnsureEmptyDirectory(cfg.cacheDir, 0700); err != nil { + setupLog.Error(err, "unable to ensure empty cache directory") + return err + } + + unpackCacheBasePath := filepath.Join(cfg.cacheDir, "unpack") + if err := os.MkdirAll(unpackCacheBasePath, 0770); err != nil { + setupLog.Error(err, "unable to create cache directory for unpacking") + return err + } + + imageCache := imageutil.CatalogCache(unpackCacheBasePath) + imagePuller := &imageutil.ContainersImagePuller{ + SourceCtxFunc: func(ctx context.Context) (*types.SystemContext, error) { + logger := log.FromContext(ctx) + srcContext := &types.SystemContext{ + DockerCertPath: cfg.pullCasDir, + OCICertPath: cfg.pullCasDir, + } + if _, err := os.Stat(authFilePath); err == nil { + logger.Info("using available authentication information for pulling image") + srcContext.AuthFilePath = authFilePath + } else if os.IsNotExist(err) { + logger.Info("no authentication information found for pulling image, proceeding without auth") + } else { + return nil, fmt.Errorf("could not stat auth file, error: %w", err) + } + return srcContext, nil + }, + } + + var localStorage storage.Instance + metrics.Registry.MustRegister(catalogdmetrics.RequestDurationMetric) + + storeDir := filepath.Join(cfg.cacheDir, storageDir) + if err := os.MkdirAll(storeDir, 0700); err != nil { + setupLog.Error(err, "unable to create storage directory for catalogs") + return err + } + + baseStorageURL, err := url.Parse(fmt.Sprintf("%s/catalogs/", cfg.externalAddr)) + if err != nil { + setupLog.Error(err, "unable to create base storage URL") + return err + } + + localStorage = &storage.LocalDirV1{ + RootDir: storeDir, + RootURL: baseStorageURL, + EnableMetasHandler: features.CatalogdFeatureGate.Enabled(features.APIV1MetasHandler), + } + + // Config for the catalogd web server + catalogServerConfig := serverutil.CatalogServerConfig{ + ExternalAddr: cfg.externalAddr, + CatalogAddr: cfg.catalogServerAddr, + CertFile: cfg.certFile, + KeyFile: cfg.keyFile, + LocalStorage: localStorage, + } + + err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig, cw) + if err != nil { + setupLog.Error(err, "unable to configure catalog server") + return err + } + + if err = (&corecontrollers.ClusterCatalogReconciler{ + Client: mgr.GetClient(), + ImageCache: imageCache, + ImagePuller: imagePuller, + Storage: localStorage, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterCatalog") + return err + } + + setupLog.Info("creating SecretSyncer controller for watching secret", "Secret", cfg.globalPullSecret) + err = (&sharedcontrollers.PullSecretReconciler{ + Client: mgr.GetClient(), + AuthFilePath: authFilePath, + SecretKey: cfg.globalPullSecretKey, + ServiceAccountKey: saKey, + }).SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SecretSyncer") + return err + } + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + return err + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + return err + } + + metaClient, err := metadata.NewForConfig(mgr.GetConfig()) + if err != nil { + setupLog.Error(err, "unable to setup client for garbage collection") + return err + } + + gc := &garbagecollection.GarbageCollector{ + CachePath: unpackCacheBasePath, + Logger: ctrl.Log.WithName("garbage-collector"), + MetadataClient: metaClient, + Interval: cfg.gcInterval, + } + if err := mgr.Add(gc); err != nil { + setupLog.Error(err, "unable to add garbage collector to manager") + return err + } + + // mutating webhook that labels ClusterCatalogs with name label + if err = (&webhook.ClusterCatalog{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "ClusterCatalog") + return err + } + + setupLog.Info("starting mutating webhook manager") + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running manager") + return err + } + if err := os.Remove(authFilePath); err != nil { + setupLog.Error(err, "failed to cleanup temporary auth file") + return err + } + return nil +} + +func podNamespace() string { + namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return "olmv1-system" + } + return string(namespace) +} diff --git a/cmd/manager/main.go b/cmd/manager/main.go deleted file mode 100644 index 95813cf6c1..0000000000 --- a/cmd/manager/main.go +++ /dev/null @@ -1,298 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "flag" - "fmt" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/spf13/pflag" - "go.uber.org/zap/zapcore" - apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - k8slabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - corev1client "k8s.io/client-go/kubernetes/typed/core/v1" - _ "k8s.io/client-go/plugin/pkg/client/auth" - ctrl "sigs.k8s.io/controller-runtime" - crcache "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/server" - - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/action" - "github.com/operator-framework/operator-controller/internal/applier" - "github.com/operator-framework/operator-controller/internal/authentication" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/cache" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" - "github.com/operator-framework/operator-controller/internal/contentmanager" - "github.com/operator-framework/operator-controller/internal/controllers" - "github.com/operator-framework/operator-controller/internal/features" - "github.com/operator-framework/operator-controller/internal/httputil" - "github.com/operator-framework/operator-controller/internal/labels" - "github.com/operator-framework/operator-controller/internal/resolve" - "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" - "github.com/operator-framework/operator-controller/internal/rukpak/source" - "github.com/operator-framework/operator-controller/internal/scheme" - "github.com/operator-framework/operator-controller/internal/version" -) - -var ( - setupLog = ctrl.Log.WithName("setup") - defaultSystemNamespace = "olmv1-system" -) - -// podNamespace checks whether the controller is running in a Pod vs. -// being run locally by inspecting the namespace file that gets mounted -// automatically for Pods at runtime. If that file doesn't exist, then -// return defaultSystemNamespace. -func podNamespace() string { - namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") - if err != nil { - return defaultSystemNamespace - } - return string(namespace) -} - -func main() { - var ( - metricsAddr string - enableLeaderElection bool - probeAddr string - cachePath string - operatorControllerVersion bool - systemNamespace string - caCertDir string - ) - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.StringVar(&caCertDir, "ca-certs-dir", "", "The directory of TLS certificate to use for verifying HTTPS connections to the Catalogd and docker-registry web servers.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching") - flag.BoolVar(&operatorControllerVersion, "version", false, "Prints operator-controller version information") - flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") - opts := zap.Options{ - Development: true, - TimeEncoder: zapcore.RFC3339NanoTimeEncoder, - } - opts.BindFlags(flag.CommandLine) - - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - features.OperatorControllerFeatureGate.AddFlag(pflag.CommandLine) - pflag.Parse() - - if operatorControllerVersion { - fmt.Println(version.String()) - os.Exit(0) - } - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts), zap.StacktraceLevel(zapcore.DPanicLevel))) - setupLog.Info("starting up the controller", "version info", version.String()) - - if systemNamespace == "" { - systemNamespace = podNamespace() - } - - dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{ocv1alpha1.ClusterExtensionKind}) - if err != nil { - setupLog.Error(err, "unable to create dependent label selector for cache") - os.Exit(1) - } - dependentSelector := k8slabels.NewSelector().Add(*dependentRequirement) - - setupLog.Info("set up manager") - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme.Scheme, - Metrics: server.Options{BindAddress: metricsAddr}, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "9c4404e7.operatorframework.io", - Cache: crcache.Options{ - ByObject: map[client.Object]crcache.ByObject{ - &ocv1alpha1.ClusterExtension{}: {Label: k8slabels.Everything()}, - &catalogd.ClusterCatalog{}: {Label: k8slabels.Everything()}, - }, - DefaultNamespaces: map[string]crcache.Config{ - systemNamespace: {LabelSelector: k8slabels.Everything()}, - }, - DefaultLabelSelector: dependentSelector, - }, - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, - }) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - coreClient, err := corev1client.NewForConfig(mgr.GetConfig()) - if err != nil { - setupLog.Error(err, "unable to create core client") - os.Exit(1) - } - tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour)) - clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter) - - cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), - helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), systemNamespace)), - helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) { - ext := obj.(*ocv1alpha1.ClusterExtension) - return ext.Spec.Install.Namespace, nil - }), - helmclient.ClientRestConfigMapper(clientRestConfigMapper), - ) - if err != nil { - setupLog.Error(err, "unable to config for creating helm client") - os.Exit(1) - } - - acg, err := action.NewWrappedActionClientGetter(cfgGetter, - helmclient.WithFailureRollbacks(false), - ) - if err != nil { - setupLog.Error(err, "unable to create helm client") - os.Exit(1) - } - - certPoolWatcher, err := httputil.NewCertPoolWatcher(caCertDir, ctrl.Log.WithName("cert-pool")) - if err != nil { - setupLog.Error(err, "unable to create CA certificate pool") - os.Exit(1) - } - unpacker := &source.ImageRegistry{ - BaseCachePath: filepath.Join(cachePath, "unpack"), - CertPoolWatcher: certPoolWatcher, - } - - clusterExtensionFinalizers := crfinalizer.NewFinalizers() - if err := clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupUnpackCacheFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { - return crfinalizer.Result{}, os.RemoveAll(filepath.Join(unpacker.BaseCachePath, obj.GetName())) - })); err != nil { - setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterExtensionCleanupUnpackCacheFinalizer) - os.Exit(1) - } - - cl := mgr.GetClient() - - catalogsCachePath := filepath.Join(cachePath, "catalogs") - if err := os.MkdirAll(catalogsCachePath, 0700); err != nil { - setupLog.Error(err, "unable to create catalogs cache directory") - os.Exit(1) - } - catalogClient := catalogclient.New(cache.NewFilesystemCache(catalogsCachePath, func() (*http.Client, error) { - return httputil.BuildHTTPClient(certPoolWatcher) - })) - - resolver := &resolve.CatalogResolver{ - WalkCatalogsFunc: resolve.CatalogWalker( - func(ctx context.Context, option ...client.ListOption) ([]catalogd.ClusterCatalog, error) { - var catalogs catalogd.ClusterCatalogList - if err := cl.List(ctx, &catalogs, option...); err != nil { - return nil, err - } - return catalogs.Items, nil - }, - catalogClient.GetPackage, - ), - Validations: []resolve.ValidationFunc{ - resolve.NoDependencyValidation, - }, - } - - aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig()) - if err != nil { - setupLog.Error(err, "unable to create apiextensions client") - os.Exit(1) - } - - preflights := []applier.Preflight{ - crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()), - } - - applier := &applier.Helm{ - ActionClientGetter: acg, - Preflights: preflights, - } - - cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) - err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { - ext := obj.(*ocv1alpha1.ClusterExtension) - err := cm.Delete(ext) - return crfinalizer.Result{}, err - })) - if err != nil { - setupLog.Error(err, "unable to register content manager cleanup finalizer") - os.Exit(1) - } - - if err = (&controllers.ClusterExtensionReconciler{ - Client: cl, - Resolver: resolver, - Unpacker: unpacker, - Applier: applier, - InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg}, - Finalizers: clusterExtensionFinalizers, - Manager: cm, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") - os.Exit(1) - } - //+kubebuilder:scaffold:builder - - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) - } - - setupLog.Info("starting manager") - ctx := ctrl.SetupSignalHandler() - if err := mgr.Start(ctx); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) - } -} - -type finalizerFunc func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) - -func (f finalizerFunc) Finalize(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { - return f(ctx, obj) -} diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go new file mode 100644 index 0000000000..b3eb066f58 --- /dev/null +++ b/cmd/operator-controller/main.go @@ -0,0 +1,704 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "crypto/tls" + "errors" + "flag" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + "go.podman.io/image/v5/types" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + k8slabels "k8s.io/apimachinery/pkg/labels" + k8stypes "k8s.io/apimachinery/pkg/types" + apimachineryrand "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/client-go/discovery" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" + "pkg.package-operator.run/boxcutter/machinery" + "pkg.package-operator.run/boxcutter/managedcache" + "pkg.package-operator.run/boxcutter/ownerhandling" + "pkg.package-operator.run/boxcutter/validation" + ctrl "sigs.k8s.io/controller-runtime" + crcache "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/client" + crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/action" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/cache" + catalogclient "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client" + "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/internal/operator-controller/features" + "github.com/operator-framework/operator-controller/internal/operator-controller/finalizers" + "github.com/operator-framework/operator-controller/internal/operator-controller/resolve" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/certproviders" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" + sharedcontrollers "github.com/operator-framework/operator-controller/internal/shared/controllers" + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" + httputil "github.com/operator-framework/operator-controller/internal/shared/util/http" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" + "github.com/operator-framework/operator-controller/internal/shared/util/pullsecretcache" + sautil "github.com/operator-framework/operator-controller/internal/shared/util/sa" + "github.com/operator-framework/operator-controller/internal/shared/util/tlsprofiles" + "github.com/operator-framework/operator-controller/internal/shared/version" +) + +var ( + setupLog = ctrl.Log.WithName("setup") + defaultSystemNamespace = "olmv1-system" + certWatcher *certwatcher.CertWatcher + cfg = &config{} +) + +type config struct { + metricsAddr string + pprofAddr string + certFile string + keyFile string + enableLeaderElection bool + probeAddr string + cachePath string + systemNamespace string + catalogdCasDir string + pullCasDir string + globalPullSecret string +} + +const ( + authFilePrefix = "operator-controller-global-pull-secrets" + fieldOwnerPrefix = "olm.operatorframework.io" +) + +// podNamespace checks whether the controller is running in a Pod vs. +// being run locally by inspecting the namespace file that gets mounted +// automatically for Pods at runtime. If that file doesn't exist, then +// return defaultSystemNamespace. +func podNamespace() string { + namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return defaultSystemNamespace + } + return string(namespace) +} + +var operatorControllerCmd = &cobra.Command{ + Use: "operator-controller", + Short: "operator-controller is the central component of Operator Lifecycle Manager (OLM) v1", + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateMetricsFlags(); err != nil { + return err + } + return run() + }, +} + +var versionCommand = &cobra.Command{ + Use: "version", + Short: "Prints operator-controller version information", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.String()) + }, +} + +func init() { + //create flagset, the collection of flags for this command + flags := operatorControllerCmd.Flags() + flags.StringVar(&cfg.metricsAddr, "metrics-bind-address", "", "The address for the metrics endpoint. Requires tls-cert and tls-key. (Default: ':8443')") + flags.StringVar(&cfg.pprofAddr, "pprof-bind-address", "0", "The address the pprof endpoint binds to. an empty string or 0 disables pprof") + flags.StringVar(&cfg.probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flags.StringVar(&cfg.catalogdCasDir, "catalogd-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to the Catalogd web service.") + flags.StringVar(&cfg.pullCasDir, "pull-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to image registries.") + flags.StringVar(&cfg.certFile, "tls-cert", "", "The certificate file used for the metrics server. Required to enable the metrics server. Requires tls-key.") + flags.StringVar(&cfg.keyFile, "tls-key", "", "The key file used for the metrics server. Required to enable the metrics server. Requires tls-cert") + flags.BoolVar(&cfg.enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flags.StringVar(&cfg.cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching") + flags.StringVar(&cfg.systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") + flags.StringVar(&cfg.globalPullSecret, "global-pull-secret", "", "The / of the global pull secret that is going to be used to pull bundle images.") + + //adds version sub command + operatorControllerCmd.AddCommand(versionCommand) + + //add klog flags to flagset + klog.InitFlags(flag.CommandLine) + flags.AddGoFlagSet(flag.CommandLine) + + //add feature gate flags to flagset + features.OperatorControllerFeatureGate.AddFlag(flags) + + //add TLS flags + tlsprofiles.AddFlags(flags) + + ctrl.SetLogger(klog.NewKlogr()) +} +func validateMetricsFlags() error { + if (cfg.certFile != "" && cfg.keyFile == "") || (cfg.certFile == "" && cfg.keyFile != "") { + setupLog.Error(errors.New("missing TLS configuration"), + "tls-cert and tls-key flags must be used together", + "certFile", cfg.certFile, "keyFile", cfg.keyFile) + return fmt.Errorf("unable to configure TLS certificates: tls-cert and tls-key flags must be used together") + } + + if cfg.metricsAddr != "" && cfg.certFile == "" && cfg.keyFile == "" { + setupLog.Error(errors.New("invalid metrics configuration"), + "metrics-bind-address requires tls-cert and tls-key flags to be set", + "metricsAddr", cfg.metricsAddr, "certFile", cfg.certFile, "keyFile", cfg.keyFile) + return fmt.Errorf("metrics-bind-address requires tls-cert and tls-key flags to be set") + } + + if cfg.certFile != "" && cfg.keyFile != "" && cfg.metricsAddr == "" { + cfg.metricsAddr = ":8443" + } + return nil +} +func run() error { + setupLog.Info("starting up the controller", "version info", version.String()) + + // log feature gate status after parsing flags and setting up logger + features.LogFeatureGateStates(setupLog, features.OperatorControllerFeatureGate) + + authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8))) + var globalPullSecretKey *k8stypes.NamespacedName + if cfg.globalPullSecret != "" { + secretParts := strings.Split(cfg.globalPullSecret, "/") + if len(secretParts) != 2 { + err := fmt.Errorf("incorrect number of components") + setupLog.Error(err, "Value of global-pull-secret should be of the format /") + return err + } + globalPullSecretKey = &k8stypes.NamespacedName{Name: secretParts[1], Namespace: secretParts[0]} + } + + if cfg.systemNamespace == "" { + cfg.systemNamespace = podNamespace() + } + + setupLog.Info("set up manager") + cacheOptions := crcache.Options{ + ByObject: map[client.Object]crcache.ByObject{ + &ocv1.ClusterExtension{}: {Label: k8slabels.Everything()}, + &ocv1.ClusterCatalog{}: {Label: k8slabels.Everything()}, + &rbacv1.ClusterRole{}: {Label: k8slabels.Everything()}, + &rbacv1.ClusterRoleBinding{}: {Label: k8slabels.Everything()}, + &rbacv1.Role{}: {Namespaces: map[string]crcache.Config{}, Label: k8slabels.Everything()}, + &rbacv1.RoleBinding{}: {Namespaces: map[string]crcache.Config{}, Label: k8slabels.Everything()}, + }, + DefaultNamespaces: map[string]crcache.Config{ + cfg.systemNamespace: {LabelSelector: k8slabels.Everything()}, + }, + DefaultLabelSelector: k8slabels.Nothing(), + } + + if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) { + cacheOptions.ByObject[&ocv1.ClusterExtensionRevision{}] = crcache.ByObject{ + Label: k8slabels.Everything(), + } + } + + saKey, err := sautil.GetServiceAccount() + if err != nil { + setupLog.Error(err, "Failed to extract serviceaccount from JWT") + return err + } + setupLog.Info("Successfully extracted serviceaccount from JWT", "serviceaccount", + fmt.Sprintf("%s/%s", saKey.Namespace, saKey.Name)) + + err = pullsecretcache.SetupPullSecretCache(&cacheOptions, globalPullSecretKey, saKey) + if err != nil { + setupLog.Error(err, "Unable to setup pull-secret cache") + return err + } + + metricsServerOptions := server.Options{} + if len(cfg.certFile) > 0 && len(cfg.keyFile) > 0 { + setupLog.Info("Starting metrics server with TLS enabled", "addr", cfg.metricsAddr, "tls-cert", cfg.certFile, "tls-key", cfg.keyFile) + + metricsServerOptions.BindAddress = cfg.metricsAddr + metricsServerOptions.SecureServing = true + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + + // If the certificate files change, the watcher will reload them. + var err error + certWatcher, err = certwatcher.New(cfg.certFile, cfg.keyFile) + if err != nil { + setupLog.Error(err, "Failed to initialize certificate watcher") + return err + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = certWatcher.GetCertificate + // If the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + // Besides, those CVEs are solved already; the solution is still insufficient, and we need to mitigate + // the risks. More info https://github.com/golang/go/issues/63417 + config.NextProtos = []string{"http/1.1"} + }) + tlsProfile, err := tlsprofiles.GetTLSConfigFunc() + if err != nil { + setupLog.Error(err, "failed to get TLS profile") + return err + } + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, tlsProfile) + } else { + // Note that the metrics server is not serving if the BindAddress is set to "0". + // Therefore, the metrics server is disabled by default. It is only enabled + // if certFile and keyFile are provided. The intention is not allowing the metrics + // be served with the default self-signed certificate generated by controller-runtime. + metricsServerOptions.BindAddress = "0" + + setupLog.Info("WARNING: Metrics Server is disabled. " + + "Metrics will not be served since the TLS certificate and key file are not provided.") + } + + restConfig := ctrl.GetConfigOrDie() + mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricsServerOptions, + PprofBindAddress: cfg.pprofAddr, + HealthProbeBindAddress: cfg.probeAddr, + LeaderElection: cfg.enableLeaderElection, + LeaderElectionID: "9c4404e7.operatorframework.io", + LeaderElectionReleaseOnCancel: true, + // Recommended Leader Election values + // https://github.com/openshift/enhancements/blob/61581dcd985130357d6e4b0e72b87ee35394bf6e/CONVENTIONS.md#handling-kube-apiserver-disruption + LeaseDuration: ptr.To(137 * time.Second), + RenewDeadline: ptr.To(107 * time.Second), + RetryPeriod: ptr.To(26 * time.Second), + + Cache: cacheOptions, + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + return err + } + + cpwCatalogd, err := httputil.NewCertPoolWatcher(cfg.catalogdCasDir, ctrl.Log.WithName("catalogd-ca-pool")) + if err != nil { + setupLog.Error(err, "unable to create catalogd-ca-pool watcher") + return err + } + cpwCatalogd.Restart(os.Exit) + if err = mgr.Add(cpwCatalogd); err != nil { + setupLog.Error(err, "unable to add catalogd-ca-pool watcher to manager") + return err + } + + // This watches the pullCasDir and the SSL_CERT_DIR, and SSL_CERT_FILE for changes + cpwPull, err := httputil.NewCertPoolWatcher(cfg.pullCasDir, ctrl.Log.WithName("pull-ca-pool")) + if err != nil { + setupLog.Error(err, "unable to create pull-ca-pool watcher") + return err + } + cpwPull.Restart(os.Exit) + if err = mgr.Add(cpwPull); err != nil { + setupLog.Error(err, "unable to add pull-ca-pool watcher to manager") + return err + } + + if certWatcher != nil { + setupLog.Info("Adding certificate watcher to manager") + if err := mgr.Add(certWatcher); err != nil { + setupLog.Error(err, "unable to add certificate watcher to manager") + return err + } + } + + if err := fsutil.EnsureEmptyDirectory(cfg.cachePath, 0700); err != nil { + setupLog.Error(err, "unable to ensure empty cache directory") + return err + } + + imageCache := imageutil.BundleCache(filepath.Join(cfg.cachePath, "unpack")) + imagePuller := &imageutil.ContainersImagePuller{ + SourceCtxFunc: func(ctx context.Context) (*types.SystemContext, error) { + srcContext := &types.SystemContext{ + DockerCertPath: cfg.pullCasDir, + OCICertPath: cfg.pullCasDir, + } + logger := log.FromContext(ctx) + if _, err := os.Stat(authFilePath); err == nil { + logger.Info("using available authentication information for pulling image") + srcContext.AuthFilePath = authFilePath + } else if os.IsNotExist(err) { + logger.Info("no authentication information found for pulling image, proceeding without auth") + } else { + return nil, fmt.Errorf("could not stat auth file, error: %w", err) + } + return srcContext, nil + }, + } + + clusterExtensionFinalizers := crfinalizer.NewFinalizers() + if err := clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupUnpackCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + return crfinalizer.Result{}, imageCache.Delete(ctx, obj.GetName()) + })); err != nil { + setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterExtensionCleanupUnpackCacheFinalizer) + return err + } + + cl := mgr.GetClient() + + catalogsCachePath := filepath.Join(cfg.cachePath, "catalogs") + if err := os.MkdirAll(catalogsCachePath, 0700); err != nil { + setupLog.Error(err, "unable to create catalogs cache directory") + return err + } + catalogClientBackend := cache.NewFilesystemCache(catalogsCachePath) + catalogClient := catalogclient.New(catalogClientBackend, func() (*http.Client, error) { + return httputil.BuildHTTPClient(cpwCatalogd) + }) + + resolver := &resolve.CatalogResolver{ + WalkCatalogsFunc: resolve.CatalogWalker( + func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) { + var catalogs ocv1.ClusterCatalogList + if err := cl.List(ctx, &catalogs, option...); err != nil { + return nil, err + } + return catalogs.Items, nil + }, + catalogClient.GetPackage, + ), + Validations: []resolve.ValidationFunc{ + resolve.NoDependencyValidation, + }, + } + + aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig()) + if err != nil { + setupLog.Error(err, "unable to create apiextensions client") + return err + } + + preflights := []applier.Preflight{ + crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()), + } + + var ctrlBuilderOpts []controllers.ControllerBuilderOption + if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) { + ctrlBuilderOpts = append(ctrlBuilderOpts, controllers.WithOwns(&ocv1.ClusterExtensionRevision{})) + } + + ceReconciler := &controllers.ClusterExtensionReconciler{ + Client: cl, + Resolver: resolver, + ImageCache: imageCache, + ImagePuller: imagePuller, + Finalizers: clusterExtensionFinalizers, + } + ceController, err := ceReconciler.SetupWithManager(mgr, ctrlBuilderOpts...) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") + return err + } + + certProvider := getCertificateProvider() + regv1ManifestProvider := &applier.RegistryV1ManifestProvider{ + BundleRenderer: registryv1.Renderer, + CertificateProvider: certProvider, + IsWebhookSupportEnabled: certProvider != nil, + IsSingleOwnNamespaceEnabled: features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport), + } + + if features.OperatorControllerFeatureGate.Enabled(features.BoxcutterRuntime) { + err = setupBoxcutter(mgr, ceReconciler, preflights, clusterExtensionFinalizers, regv1ManifestProvider) + } else { + err = setupHelm(mgr, ceReconciler, preflights, ceController, clusterExtensionFinalizers, regv1ManifestProvider) + } + if err != nil { + setupLog.Error(err, "unable to setup lifecycler") + return err + } + + if err = (&controllers.ClusterCatalogReconciler{ + Client: cl, + CatalogCache: catalogClientBackend, + CatalogCachePopulator: catalogClient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterCatalog") + return err + } + + setupLog.Info("creating SecretSyncer controller for watching secret", "Secret", cfg.globalPullSecret) + err = (&sharedcontrollers.PullSecretReconciler{ + Client: mgr.GetClient(), + AuthFilePath: authFilePath, + SecretKey: globalPullSecretKey, + ServiceAccountKey: saKey, + }).SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SecretSyncer") + return err + } + + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + return err + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + return err + } + + setupLog.Info("starting manager") + ctx := ctrl.SetupSignalHandler() + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running manager") + return err + } + if err := os.Remove(authFilePath); err != nil { + setupLog.Error(err, "failed to cleanup temporary auth file") + return err + } + return nil +} + +func getCertificateProvider() render.CertificateProvider { + if features.OperatorControllerFeatureGate.Enabled(features.WebhookProviderCertManager) { + return certproviders.CertManagerCertificateProvider{} + } else if features.OperatorControllerFeatureGate.Enabled(features.WebhookProviderOpenshiftServiceCA) { + return certproviders.OpenshiftServiceCaCertificateProvider{} + } + return nil +} + +func setupBoxcutter( + mgr manager.Manager, + ceReconciler *controllers.ClusterExtensionReconciler, + preflights []applier.Preflight, + clusterExtensionFinalizers crfinalizer.Registerer, + regv1ManifestProvider applier.ManifestProvider, +) error { + coreClient, err := corev1client.NewForConfig(mgr.GetConfig()) + if err != nil { + return fmt.Errorf("unable to create core client: %w", err) + } + cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), + helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)), + helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) { + ext := obj.(*ocv1.ClusterExtension) + return ext.Spec.Namespace, nil + }), + ) + if err != nil { + return fmt.Errorf("unable to create helm action config getter: %w", err) + } + + acg, err := action.NewWrappedActionClientGetter(cfgGetter, + helmclient.WithFailureRollbacks(false), + ) + if err != nil { + return fmt.Errorf("unable to create helm action client getter: %w", err) + } + + // Register a no-op finalizer handler for cleanup-contentmanager-cache. + // This finalizer was added by the Helm applier for ClusterExtensions created + // before BoxcutterRuntime was enabled. Boxcutter doesn't use contentmanager, + // so we just need to acknowledge the finalizer to allow deletion to proceed. + err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + // No-op: Boxcutter doesn't use contentmanager, so no cleanup is needed + return crfinalizer.Result{}, nil + })) + if err != nil { + setupLog.Error(err, "unable to register content manager cleanup finalizer for boxcutter") + return err + } + + // TODO: add support for preflight checks + // TODO: better scheme handling - which types do we want to support? + _ = apiextensionsv1.AddToScheme(mgr.GetScheme()) + rg := &applier.SimpleRevisionGenerator{ + Scheme: mgr.GetScheme(), + ManifestProvider: regv1ManifestProvider, + } + ceReconciler.Applier = &applier.Boxcutter{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + RevisionGenerator: rg, + Preflights: preflights, + FieldOwner: fmt.Sprintf("%s/clusterextension-controller", fieldOwnerPrefix), + } + ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()} + ceReconciler.StorageMigrator = &applier.BoxcutterStorageMigrator{ + Client: mgr.GetClient(), + ActionClientGetter: acg, + RevisionGenerator: rg, + } + + discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig()) + if err != nil { + return fmt.Errorf("unable to create discovery client: %w", err) + } + + trackingCache, err := managedcache.NewTrackingCache( + ctrl.Log.WithName("trackingCache"), + mgr.GetConfig(), + crcache.Options{ + Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(), + }, + ) + if err != nil { + return fmt.Errorf("unable to create boxcutter tracking cache: %v", err) + } + if err := mgr.Add(trackingCache); err != nil { + return fmt.Errorf("unable to add tracking cache to manager: %v", err) + } + + if err = (&controllers.ClusterExtensionRevisionReconciler{ + Client: mgr.GetClient(), + RevisionEngine: machinery.NewRevisionEngine( + machinery.NewPhaseEngine( + machinery.NewObjectEngine( + mgr.GetScheme(), trackingCache, mgr.GetClient(), + ownerhandling.NewNative(mgr.GetScheme()), + machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), fieldOwnerPrefix), + fieldOwnerPrefix, fieldOwnerPrefix, + ), + validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()), + ), + validation.NewRevisionValidator(), mgr.GetClient(), + ), + TrackingCache: trackingCache, + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to setup ClusterExtensionRevision controller: %w", err) + } + return nil +} + +func setupHelm( + mgr manager.Manager, + ceReconciler *controllers.ClusterExtensionReconciler, + preflights []applier.Preflight, + ceController crcontroller.Controller, + clusterExtensionFinalizers crfinalizer.Registerer, + regv1ManifestProvider applier.ManifestProvider, +) error { + coreClient, err := corev1client.NewForConfig(mgr.GetConfig()) + if err != nil { + return fmt.Errorf("unable to create core client: %w", err) + } + tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour)) + clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter) + if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) { + clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper) + } + + cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), + helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)), + helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) { + ext := obj.(*ocv1.ClusterExtension) + return ext.Spec.Namespace, nil + }), + helmclient.ClientRestConfigMapper(clientRestConfigMapper), + ) + if err != nil { + return fmt.Errorf("unable to create helm action config getter: %w", err) + } + + acg, err := action.NewWrappedActionClientGetter(cfgGetter, + helmclient.WithFailureRollbacks(false), + ) + if err != nil { + return fmt.Errorf("unable to create helm action client getter: %w", err) + } + + // determine if PreAuthorizer should be enabled based on feature gate + var preAuth authorization.PreAuthorizer + if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { + preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient()) + } + + cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()) + err = clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupContentManagerCacheFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + ext := obj.(*ocv1.ClusterExtension) + err := cm.Delete(ext) + return crfinalizer.Result{}, err + })) + if err != nil { + setupLog.Error(err, "unable to register content manager cleanup finalizer") + return err + } + + // now initialize the helmApplier, assigning the potentially nil preAuth + ceReconciler.Applier = &applier.Helm{ + ActionClientGetter: acg, + Preflights: preflights, + HelmChartProvider: &applier.RegistryV1HelmChartProvider{ + ManifestProvider: regv1ManifestProvider, + }, + HelmReleaseToObjectsConverter: &applier.HelmReleaseToObjectsConverter{}, + PreAuthorizer: preAuth, + Watcher: ceController, + Manager: cm, + } + ceReconciler.RevisionStatesGetter = &controllers.HelmRevisionStatesGetter{ActionClientGetter: acg} + return nil +} + +func main() { + if err := operatorControllerCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/codecov.yml b/codecov.yml index 4dde336da3..bbe044b0f5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,27 @@ codecov: notify: - after_n_builds: 2 + # Configure the 4 builds to wait before sending a notification. + # test-unit, test-regression, test-e2e and test-experimental-e2e. + after_n_builds: 4 + +# Configure the paths to include in coverage reports. +# Exclude documentation, YAML configurations, and test files. +coverage: + status: + project: + default: + target: auto + threshold: 2% + paths: + - "api/" + - "cmd/" + - "internal/" + patch: + default: + target: auto + threshold: 1% + paths: + - "api/" + - "cmd/" + - "internal/" + diff --git a/config/OWNERS b/config/OWNERS new file mode 100644 index 0000000000..b44dad0ea8 --- /dev/null +++ b/config/OWNERS @@ -0,0 +1,2 @@ +approvers: + - manifest-approvers diff --git a/config/README.md b/config/README.md index 1f80115074..6fe4ab8892 100644 --- a/config/README.md +++ b/config/README.md @@ -1,52 +1,5 @@ -# OPERATOR-CONTROLLER CONFIG +# OPERATOR-CONTROLLER CONFIGURATION -## config/base +## Samples -This provides an insecure (i.e. no TLS) basic configuration of operator-controller. - -This configuration specifies a namespace of `olmv1-system`. - -## config/overlays/cert-manager - -This includes support for a secure (i.e. with TLS) configuration of operator-controller. This configuration uses: -* config/base -* config/components/tls -* config/components/ca - -This configuration requires cert-manager. - -## config/overlays/e2e - -This provides additional configuration support for end-to-end testing, including code coverage. This configuration uses: -* config/base -* config/components/tls -* config/components/ca -* config/components/coverage - -This configuration requires cert-manager. - -## Components - -Each of the `kustomization.yaml` files specify a `Component`, rather than an overlay. - -### config/components/tls - -This provides a basic configuration of operator-controller with TLS support for catalogd. - -This component specifies the `olmv1-system` namespace. - -This component requires cert-manager. - -### config/components/coverage - -Provides configuration for code coverage. - -This component specifies the `olmv1-system` namespace. - -### config/components/ca - -Procides a CA for operator-controller operation. - -This component _does not_ specify a namespace, and must be included last. - -This component requires cert-manager. +The `config/samples` directory contains example ClusterCatalog and ClusterExtension resources. diff --git a/config/base/crd/kustomization.yaml b/config/base/crd/kustomization.yaml deleted file mode 100644 index ec864639d2..0000000000 --- a/config/base/crd/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# This kustomization.yaml is not intended to be run by itself, -# since it depends on service name and namespace that are out of this kustomize package. -# It should be run by config/default -resources: -- bases/olm.operatorframework.io_clusterextensions.yaml - -# the following config is for teaching kustomize how to do kustomization for CRDs. -configurations: -- kustomizeconfig.yaml diff --git a/config/base/crd/kustomizeconfig.yaml b/config/base/crd/kustomizeconfig.yaml deleted file mode 100644 index ec5c150a9d..0000000000 --- a/config/base/crd/kustomizeconfig.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This file is for teaching kustomize how to substitute name and namespace reference in CRD -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/name - -namespace: -- kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/namespace - create: false - -varReference: -- path: metadata/annotations diff --git a/config/base/kustomization.yaml b/config/base/kustomization.yaml deleted file mode 100644 index 12884c03c6..0000000000 --- a/config/base/kustomization.yaml +++ /dev/null @@ -1,136 +0,0 @@ -# Adds namespace to all resources. -namespace: olmv1-system - -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. -namePrefix: operator-controller- - -# Labels to add to all resources and selectors. -#labels: -#- includeSelectors: true -# pairs: -# someName: someValue - -resources: -- crd -- rbac -- manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager -# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus - -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml -#- manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -# Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: -# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.namespace # namespace of the certificate CR -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - source: # Add cert-manager annotation to the webhook Service -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 0 -# create: true -# - source: -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.namespace # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true diff --git a/config/base/manager/kustomization.yaml b/config/base/manager/kustomization.yaml deleted file mode 100644 index 28e97824fe..0000000000 --- a/config/base/manager/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -resources: -- manager.yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -images: -- name: controller - newName: quay.io/operator-framework/operator-controller - newTag: devel \ No newline at end of file diff --git a/config/base/manager/manager.yaml b/config/base/manager/manager.yaml deleted file mode 100644 index 67dedaf383..0000000000 --- a/config/base/manager/manager.yaml +++ /dev/null @@ -1,112 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - pod-security.kubernetes.io/enforce: restricted - pod-security.kubernetes.io/enforce-version: latest - name: system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - annotations: - kubectl.kubernetes.io/default-logs-container: manager - labels: - control-plane: operator-controller-controller-manager -spec: - selector: - matchLabels: - control-plane: operator-controller-controller-manager - replicas: 1 - template: - metadata: - annotations: - kubectl.kubernetes.io/default-container: manager - labels: - control-plane: operator-controller-controller-manager - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/arch - operator: In - values: - - amd64 - - arm64 - - ppc64le - - s390x - - key: kubernetes.io/os - operator: In - values: - - linux - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - containers: - - command: - - /manager - args: - - "--health-probe-bind-address=:8081" - - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" - image: controller:latest - imagePullPolicy: IfNotPresent - name: manager - volumeMounts: - - name: cache - mountPath: /var/cache - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - "ALL" - livenessProbe: - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 10 - # TODO(user): Configure the resources accordingly based on the project requirements. - # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - resources: - requests: - cpu: 10m - memory: 64Mi - terminationMessagePolicy: FallbackToLogsOnError - - name: kube-rbac-proxy - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - "ALL" - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 - args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" - ports: - - containerPort: 8443 - protocol: TCP - name: https - resources: - requests: - cpu: 5m - memory: 64Mi - terminationMessagePolicy: FallbackToLogsOnError - serviceAccountName: operator-controller-controller-manager - terminationGracePeriodSeconds: 10 - volumes: - - name: cache - emptyDir: {} diff --git a/config/base/prometheus/kustomization.yaml b/config/base/prometheus/kustomization.yaml deleted file mode 100644 index ed137168a1..0000000000 --- a/config/base/prometheus/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: -- monitor.yaml diff --git a/config/base/prometheus/monitor.yaml b/config/base/prometheus/monitor.yaml deleted file mode 100644 index 7e5c2db13a..0000000000 --- a/config/base/prometheus/monitor.yaml +++ /dev/null @@ -1,20 +0,0 @@ - -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: operator-controller-controller-manager - name: controller-manager-metrics-monitor - namespace: system -spec: - endpoints: - - path: /metrics - port: https - scheme: https - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token - tlsConfig: - insecureSkipVerify: true - selector: - matchLabels: - control-plane: operator-controller-controller-manager diff --git a/config/base/rbac/auth_proxy_role.yaml b/config/base/rbac/auth_proxy_role.yaml deleted file mode 100644 index 80e1857c59..0000000000 --- a/config/base/rbac/auth_proxy_role.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: proxy-role -rules: -- apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create diff --git a/config/base/rbac/auth_proxy_service.yaml b/config/base/rbac/auth_proxy_service.yaml deleted file mode 100644 index ae58ae83bd..0000000000 --- a/config/base/rbac/auth_proxy_service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - control-plane: operator-controller-controller-manager - name: controller-manager-metrics-service - namespace: system -spec: - ports: - - name: https - port: 8443 - protocol: TCP - targetPort: https - selector: - control-plane: operator-controller-controller-manager diff --git a/config/base/rbac/clusterextension_editor_role.yaml b/config/base/rbac/clusterextension_editor_role.yaml deleted file mode 100644 index 61cd61ce3b..0000000000 --- a/config/base/rbac/clusterextension_editor_role.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# permissions for end users to edit cluster extensions. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: clusterextension-editor-role -rules: -- apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/config/base/rbac/clusterextension_viewer_role.yaml b/config/base/rbac/clusterextension_viewer_role.yaml deleted file mode 100644 index bee8b9d9ea..0000000000 --- a/config/base/rbac/clusterextension_viewer_role.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# permissions for end users to view cluster extensions. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: clusterextension-viewer-role -rules: -- apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions - verbs: - - get - - list - - watch diff --git a/config/base/rbac/extension_editor_role.yaml b/config/base/rbac/extension_editor_role.yaml deleted file mode 100644 index caa26cfd21..0000000000 --- a/config/base/rbac/extension_editor_role.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# permissions for end users to edit extensions. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: extension-editor-role -rules: -- apiGroups: - - olm.operatorframework.io - resources: - - extensions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/config/base/rbac/extension_viewer_role.yaml b/config/base/rbac/extension_viewer_role.yaml deleted file mode 100644 index 980be2d77d..0000000000 --- a/config/base/rbac/extension_viewer_role.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# permissions for end users to view extensions. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: extension-viewer-role -rules: -- apiGroups: - - olm.operatorframework.io - resources: - - extensions - verbs: - - get - - list - - watch diff --git a/config/base/rbac/kustomization.yaml b/config/base/rbac/kustomization.yaml deleted file mode 100644 index 33b8765d5a..0000000000 --- a/config/base/rbac/kustomization.yaml +++ /dev/null @@ -1,26 +0,0 @@ -resources: -# All RBAC will be applied under this service account in -# the deployment namespace. You may comment out this resource -# if your manager will use a service account that exists at -# runtime. Be sure to update RoleBinding and ClusterRoleBinding -# subjects if changing service account names. -- service_account.yaml -- role.yaml -- role_binding.yaml -- leader_election_role.yaml -- leader_election_role_binding.yaml - -# The following resources are pre-defined roles for editors and viewers -# of APIs provided by this project. -- clusterextension_editor_role.yaml -- clusterextension_viewer_role.yaml -- extension_editor_role.yaml -- extension_viewer_role.yaml - -# Comment the following 4 lines if you want to disable -# the auth proxy (https://github.com/brancz/kube-rbac-proxy) -# which protects your /metrics endpoint. -- auth_proxy_service.yaml -- auth_proxy_role.yaml -- auth_proxy_role_binding.yaml -- auth_proxy_client_clusterrole.yaml diff --git a/config/base/rbac/leader_election_role_binding.yaml b/config/base/rbac/leader_election_role_binding.yaml deleted file mode 100644 index 1d1321ed4f..0000000000 --- a/config/base/rbac/leader_election_role_binding.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: leader-election-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: leader-election-role -subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system diff --git a/config/base/rbac/role.yaml b/config/base/rbac/role.yaml deleted file mode 100644 index 38d394780f..0000000000 --- a/config/base/rbac/role.yaml +++ /dev/null @@ -1,68 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: manager-role -rules: -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get -- apiGroups: - - "" - resources: - - serviceaccounts/token - verbs: - - create -- apiGroups: - - olm.operatorframework.io - resources: - - clustercatalogs - verbs: - - list - - watch -- apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions - verbs: - - get - - list - - patch - - update - - watch -- apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions/finalizers - verbs: - - update -- apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions/status - verbs: - - patch - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: manager-role - namespace: system -rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch diff --git a/config/base/rbac/role_binding.yaml b/config/base/rbac/role_binding.yaml deleted file mode 100644 index fa331e3d41..0000000000 --- a/config/base/rbac/role_binding.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: manager-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: manager-role -subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: manager-rolebinding - namespace: system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: manager-role -subjects: - - kind: ServiceAccount - name: controller-manager - namespace: system diff --git a/config/base/rbac/service_account.yaml b/config/base/rbac/service_account.yaml deleted file mode 100644 index 7cd6025bfc..0000000000 --- a/config/base/rbac/service_account.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: controller-manager - namespace: system diff --git a/config/components/ca/issuers.yaml b/config/components/ca/issuers.yaml deleted file mode 100644 index 0dffee04e1..0000000000 --- a/config/components/ca/issuers.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: self-sign-issuer - namespace: cert-manager -spec: - selfSigned: {} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: olmv1-ca - namespace: cert-manager -spec: - isCA: true - commonName: olmv1-ca - secretName: olmv1-ca - privateKey: - algorithm: ECDSA - size: 256 - issuerRef: - name: self-sign-issuer - kind: Issuer - group: cert-manager.io ---- -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: olmv1-ca -spec: - ca: - secretName: olmv1-ca diff --git a/config/components/ca/kustomization.yaml b/config/components/ca/kustomization.yaml deleted file mode 100644 index 5cbe13ad21..0000000000 --- a/config/components/ca/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -# No namespace is specified here, otherwise, it will overwrite _all_ the other namespaces! -resources: -- issuers.yaml diff --git a/config/components/coverage/kustomization.yaml b/config/components/coverage/kustomization.yaml deleted file mode 100644 index 5522eb7f83..0000000000 --- a/config/components/coverage/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -namespace: olmv1-system -resources: -- manager_e2e_coverage_pvc.yaml -- manager_e2e_coverage_copy_pod.yaml -patches: -- path: manager_e2e_coverage_patch.yaml diff --git a/config/components/coverage/manager_e2e_coverage_copy_pod.yaml b/config/components/coverage/manager_e2e_coverage_copy_pod.yaml deleted file mode 100644 index 7794ba97d6..0000000000 --- a/config/components/coverage/manager_e2e_coverage_copy_pod.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: e2e-coverage-copy-pod -spec: - restartPolicy: Never - securityContext: - runAsNonRoot: true - runAsUser: 65532 - seccompProfile: - type: RuntimeDefault - containers: - - name: tar - image: busybox:1.36 - command: ["sleep", "infinity"] - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - "ALL" - terminationMessagePolicy: FallbackToLogsOnError - volumeMounts: - - name: e2e-coverage-volume - mountPath: /e2e-coverage - readOnly: true - volumes: - - name: e2e-coverage-volume - persistentVolumeClaim: - claimName: e2e-coverage - readOnly: true diff --git a/config/components/coverage/manager_e2e_coverage_patch.yaml b/config/components/coverage/manager_e2e_coverage_patch.yaml deleted file mode 100644 index bda011daf6..0000000000 --- a/config/components/coverage/manager_e2e_coverage_patch.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: kube-rbac-proxy - - name: manager - env: - - name: GOCOVERDIR - value: /e2e-coverage - volumeMounts: - - name: e2e-coverage-volume - mountPath: /e2e-coverage - volumes: - - name: e2e-coverage-volume - persistentVolumeClaim: - claimName: e2e-coverage diff --git a/config/components/coverage/manager_e2e_coverage_pvc.yaml b/config/components/coverage/manager_e2e_coverage_pvc.yaml deleted file mode 100644 index 126d4d4e63..0000000000 --- a/config/components/coverage/manager_e2e_coverage_pvc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: e2e-coverage -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 64Mi diff --git a/config/components/tls/kustomization.yaml b/config/components/tls/kustomization.yaml deleted file mode 100644 index 8c1aa94ccf..0000000000 --- a/config/components/tls/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -namespace: olmv1-system -resources: -- resources/manager_cert.yaml -patches: -- target: - kind: Deployment - name: controller-manager - path: patches/manager_deployment_cert.yaml diff --git a/config/components/tls/patches/manager_deployment_cert.yaml b/config/components/tls/patches/manager_deployment_cert.yaml deleted file mode 100644 index 747979321b..0000000000 --- a/config/components/tls/patches/manager_deployment_cert.yaml +++ /dev/null @@ -1,9 +0,0 @@ -- op: add - path: /spec/template/spec/volumes/- - value: {"name":"olmv1-certificate", "secret":{"secretName":"olmv1-cert", "optional": false, "items": [{"key": "ca.crt", "path": "olm-ca.crt"}]}} -- op: add - path: /spec/template/spec/containers/0/volumeMounts/- - value: {"name":"olmv1-certificate", "readOnly": true, "mountPath":"/var/certs/"} -- op: add - path: /spec/template/spec/containers/0/args/- - value: "--ca-certs-dir=/var/certs" diff --git a/config/components/tls/resources/manager_cert.yaml b/config/components/tls/resources/manager_cert.yaml deleted file mode 100644 index a7a19f4dd1..0000000000 --- a/config/components/tls/resources/manager_cert.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: olmv1-cert -spec: - secretName: olmv1-cert - dnsNames: - - operator-controller.olmv1-system.svc - - operator-controller.olmv1-system.svc.cluster.local - privateKey: - algorithm: ECDSA - size: 256 - issuerRef: - name: olmv1-ca - kind: ClusterIssuer - group: cert-manager.io diff --git a/config/overlays/cert-manager/kustomization.yaml b/config/overlays/cert-manager/kustomization.yaml deleted file mode 100644 index 86746375ba..0000000000 --- a/config/overlays/cert-manager/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# kustomization file for secure operator-controller -# DO NOT ADD A NAMESPACE HERE -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../base -components: -- ../../components/tls -# ca must be last or tls will overwrite the namespaces -- ../../components/ca diff --git a/config/overlays/e2e/kustomization.yaml b/config/overlays/e2e/kustomization.yaml deleted file mode 100644 index 626ecb6196..0000000000 --- a/config/overlays/e2e/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# kustomization file for all the e2e's -# DO NOT ADD A NAMESPACE HERE -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../base -components: -- ../../components/tls -- ../../components/coverage -# ca must be last or (tls|coverage) will overwrite the namespaces -- ../../components/ca diff --git a/config/samples/catalogd_operatorcatalog.yaml b/config/samples/catalogd_operatorcatalog.yaml index 5965edeb1f..5e27729fa2 100644 --- a/config/samples/catalogd_operatorcatalog.yaml +++ b/config/samples/catalogd_operatorcatalog.yaml @@ -1,10 +1,11 @@ -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: name: operatorhubio spec: + priority: 0 source: - type: image + type: Image image: + pollIntervalMinutes: 10 ref: quay.io/operatorhubio/catalog:latest - pollInterval: 10m diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index bd17831769..2a64d96c2d 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,5 +1,4 @@ ## Append samples of your project ## resources: -- olm_v1alpha1_clusterextension.yaml -- olm_v1alpha1_extension.yaml +- olm_v1_clusterextension.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/olm_v1alpha1_clusterextension.yaml b/config/samples/olm_v1_clusterextension.yaml similarity index 89% rename from config/samples/olm_v1alpha1_clusterextension.yaml rename to config/samples/olm_v1_clusterextension.yaml index 19bbf06b0e..14c8e167e0 100644 --- a/config/samples/olm_v1alpha1_clusterextension.yaml +++ b/config/samples/olm_v1_clusterextension.yaml @@ -33,6 +33,10 @@ rules: resources: [clusterextensions/finalizers] verbs: [update] resourceNames: [argocd] +# Allow ClusterExtensionRevisions to set blockOwnerDeletion ownerReferences +- apiGroups: [olm.operatorframework.io] + resources: [clusterextensionrevisions/finalizers] + verbs: [update] # Manage ArgoCD CRDs - apiGroups: [apiextensions.k8s.io] resources: [customresourcedefinitions] @@ -49,20 +53,20 @@ rules: # Manage ArgoCD ClusterRoles and ClusterRoleBindings - apiGroups: [rbac.authorization.k8s.io] resources: [clusterroles] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [rbac.authorization.k8s.io] resources: [clusterroles] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx - argocd-operator-metrics-reader - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 - apiGroups: [rbac.authorization.k8s.io] resources: [clusterrolebindings] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [rbac.authorization.k8s.io] resources: [clusterrolebindings] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 @@ -226,31 +230,34 @@ metadata: rules: - apiGroups: [""] resources: [serviceaccounts] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [""] resources: [serviceaccounts] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager] - apiGroups: [""] resources: [configmaps] - verbs: [create] + verbs: [create, list, watch] +- apiGroups: [coordination.k8s.io] + resources: [leases] + verbs: [get, list, watch, create, update, patch, delete] - apiGroups: [""] resources: [configmaps] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-manager-config] - apiGroups: [""] resources: [services] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [""] resources: [services] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager-metrics-service] - apiGroups: [apps] resources: [deployments] - verbs: [create] + verbs: [create, list, watch] - apiGroups: [apps] resources: [deployments] - verbs: [get, list, watch, update, patch, delete] + verbs: [get, update, patch, delete] resourceNames: [argocd-operator-controller-manager] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -267,17 +274,16 @@ subjects: name: argocd-installer namespace: argocd --- -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: argocd spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator version: 0.6.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer diff --git a/crd-diff-config.yaml b/crd-diff-config.yaml new file mode 100644 index 0000000000..8cce393788 --- /dev/null +++ b/crd-diff-config.yaml @@ -0,0 +1,109 @@ +checks: + crd: + scope: + enabled: true + existingFieldRemoval: + enabled: true + storedVersionRemoval: + enabled: true + version: + sameVersion: + enabled: true + unhandledFailureMode: "Closed" + enum: + enabled: true + removalEnforcement: "Strict" + additionEnforcement: "Strict" + default: + enabled: true + changeEnforcement: "Strict" + removalEnforcement: "Strict" + additionEnforcement: "Strict" + required: + enabled: true + newEnforcement: "Strict" + type: + enabled: true + changeEnforcement: "Strict" + maximum: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxItems: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxProperties: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxLength: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + minimum: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minItems: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minProperties: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minLength: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + servedVersion: + enabled: true + unhandledFailureMode: "Closed" + enum: + enabled: true + removalEnforcement: "Strict" + additionEnforcement: "Strict" + default: + enabled: true + changeEnforcement: "Strict" + removalEnforcement: "Strict" + additionEnforcement: "Strict" + required: + enabled: true + newEnforcement: "Strict" + type: + enabled: true + changeEnforcement: "Strict" + maximum: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxItems: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxProperties: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + maxLength: + enabled: true + additionEnforcement: "Strict" + decreaseEnforcement: "Strict" + minimum: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minItems: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minProperties: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" + minLength: + enabled: true + additionEnforcement: "Strict" + increaseEnforcement: "Strict" diff --git a/dev/local-debugging-with-tilt-and-vscode.md b/dev/local-debugging-with-tilt-and-vscode.md new file mode 100644 index 0000000000..b74678b5bb --- /dev/null +++ b/dev/local-debugging-with-tilt-and-vscode.md @@ -0,0 +1,48 @@ +# Local Debugging in VSCode with Tilt + +This tutorial will show you how to connect the go debugger in VSCode to your running +kind cluster with Tilt for live debugging. + +* Follow the instructions in [this document](podman/setup-local-env-podman.md) to set up your local kind cluster and image registry. +* Next, execute `tilt up` to start the Tilt service (if using podman, you might need to run `DOCKER_BUILDKIT=0 tilt up`). + +Press space to open the web UI where you can monitor the current status of operator-controller and catalogd inside Tilt. + +Create a `launch.json` file in your operator-controller repository if you do not already have one. +Add the following configurations: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug operator-controller via Tilt", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 30000, + "host": "localhost", + "cwd": "${workspaceFolder}", + "trace": "verbose" + }, + { + "name": "Debug catalogd via Tilt", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 20000, + "host": "localhost", + "cwd": "${workspaceFolder}", + "trace": "verbose" + }, + ] +} +``` + +This creates two "Run and debug" entries in the Debug panel of VSCode. + +Now you can start either debug configuration depending on which component you want to debug. +VSCode will connect the debugger to the port exposed by Tilt. + +Breakpoints should now be fully functional. The debugger can even maintain its +connection through live code updates. \ No newline at end of file diff --git a/dev/podman/setup-local-env-podman.md b/dev/podman/setup-local-env-podman.md index 3328caac09..5cbb168378 100644 --- a/dev/podman/setup-local-env-podman.md +++ b/dev/podman/setup-local-env-podman.md @@ -1,32 +1,36 @@ -## The following are Podman specific steps used to set up on a MacBook (Intel or Apple Silicon) +# Configuring Podman for Tilt -### Verify installed tools (install if needed) +The following tutorial explains how to set up a local development environment using Podman and Tilt on a Linux host. +A few notes on achieving the same result for MacOS are included at the end, but you will likely need to do some +tinkering on your own. + +## Verify installed tools (install if needed) + +Ensure you have installed [Podman](https://podman.io/), [Kind](https://github.com/kubernetes-sigs/kind/), and [Tilt](https://tilt.dev/). ```sh $ podman --version podman version 5.0.1 $ kind version -kind v0.23.0 go1.22.3 darwin/arm64 - -(optional) +kind v0.26.0 go1.23.4 linux/amd64 $ tilt version -v0.33.12, built 2024-03-28 +v0.33.15, built 2024-05-31 ``` -### Start Kind with a local registry -Use this [helper script](./kind-with-registry-podman.sh) to create a local single-node Kind cluster with an attached local image registry. +## Start Kind with a local registry -#### Disable secure access on the local kind registry: +Use this [helper script](./kind-with-registry-podman.sh) to create a local single-node Kind cluster with an attached local image registry. -`podman inspect kind-registry --format '{{.NetworkSettings.Ports}}'` -With the port you find for 127.0.0.1 edit the Podman machine's config file: +## Disable secure access on the local kind registry: -`podman machine ssh` +Verify the port used by the image registry: -`sudo vi /etc/containers/registries.conf.d/100-kind.conf` +```sh +podman inspect kind-registry --format '{{.NetworkSettings.Ports}}' +``` -Should look like: +Edit `/etc/containers/registries.conf.d/100-kind.conf` so it contains the following, substituting 5001 if your registry is using a different port: ```ini [[registry]] @@ -34,21 +38,66 @@ location = "localhost:5001" insecure = true ``` -### export DOCKER_HOST +## Configure the Podman socket -`export DOCKER_HOST=unix:///var/run/docker.sock` +Tilt needs to connect to the Podman socket to initiate image builds. The socket address can differ +depending on your host OS and whether you want to use rootful or rootless Podman. If you're not sure, +you should use rootless. +You can start the rootless Podman socket by running `podman --user start podman.socket`. +If you would like to automatically start the socket in your user session, you can run +`systemctl --user enable --now podman.socket`. -### Optional - Start tilt with the tilt file in the parent directory +Find the location of your user socket with `systemctl --user status podman.socket`: -`DOCKER_BUILDKIT=0 tilt up` +```sh +● podman.socket - Podman API Socket + Loaded: loaded (/usr/lib/systemd/user/podman.socket; enabled; preset: disabled) + Active: active (listening) since Tue 2025-01-28 11:40:50 CST; 7s ago + Invocation: d9604e587f2a4581bc79cbe4efe9c7e7 + Triggers: ● podman.service + Docs: man:podman-system-service(1) + Listen: /run/user/1000/podman/podman.sock (Stream) + CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/podman.socket +``` -### Optional troubleshooting +The location of the socket is shown in the `Listen` section, which in the example above +is `/run/user/1000/podman/podman.sock`. -In some cases it may be needed to do +Set `DOCKER_HOST` to a unix address at the socket location: + +```sh +export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock ``` -sudo podman-mac-helper install + +Some systems might symlink the Podman socket to a docker socket, in which case +you might need to try something like: + +```sh +export DOCKER_HOST=unix:///var/run/docker.sock ``` + +## Start Tilt + +Running Tilt with a container engine other than Docker requires setting `DOCKER_BUILDKIT=0`. +You can export this, or just run: + +```sh +DOCKER_BUILDKIT=0 tilt up ``` + +## MacOS Troubleshooting + +The instructions above are written for use on a Linux system. You should be able to create +the same or a similar configuration on MacOS, but specific steps will differ. + +In some cases you might need to run: + +```sh +sudo podman-mac-helper install + podman machine stop/start ``` + +When disabling secure access to the registry, you will need to first enter the Podman virtual machine: +`podman machine ssh` diff --git a/docs/OWNERS b/docs/OWNERS new file mode 100644 index 0000000000..342c5f6315 --- /dev/null +++ b/docs/OWNERS @@ -0,0 +1,2 @@ +approvers: + - docs-approvers diff --git a/docs/Tasks/exploring-available-packages.md b/docs/Tasks/exploring-available-packages.md deleted file mode 100644 index eb3e1499aa..0000000000 --- a/docs/Tasks/exploring-available-packages.md +++ /dev/null @@ -1,146 +0,0 @@ -# Exploring Available Packages - -After you add a catalog of extensions to your cluster, you must port forward your catalog as a service. -Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. - -## Prerequisites - -* You have added a ClusterCatalog of extensions, such as [OperatorHub.io](https://operatorhub.io), to your cluster. -* You have installed the `jq` CLI tool. - -**Note:** By default, Catalogd is installed with TLS enabled for the catalog webserver. -The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag. - -## Procedure - -1. Port forward the catalog server service: - - ``` terminal - kubectl -n olmv1-system port-forward svc/catalogd-catalogserver 8443:443 - ``` - -2. Return a list of all the extensions in a catalog: - ``` terminal - curl -k https://localhost:8443/catalogs/operatorhubio/all.json | jq -s '.[] | select(.schema == "olm.package") | .name' - ``` - - ??? success - ``` text title="Example output" - "ack-acm-controller" - "ack-acmpca-controller" - "ack-apigatewayv2-controller" - "ack-applicationautoscaling-controller" - "ack-cloudfront-controller" - "ack-cloudtrail-controller" - "ack-cloudwatch-controller" - "ack-cloudwatchlogs-controller" - "ack-dynamodb-controller" - "ack-ec2-controller" - "ack-ecr-controller" - "ack-ecs-controller" - "ack-efs-controller" - "ack-eks-controller" - "ack-elasticache-controller" - "ack-emrcontainers-controller" - "ack-eventbridge-controller" - "ack-iam-controller" - "ack-kafka-controller" - "ack-keyspaces-controller" - "ack-kinesis-controller" - "ack-kms-controller" - "ack-lambda-controller" - "ack-memorydb-controller" - "ack-mq-controller" - "ack-networkfirewall-controller" - "ack-opensearchservice-controller" - "ack-pipes-controller" - "ack-prometheusservice-controller" - "ack-rds-controller" - "ack-route53-controller" - "ack-route53resolver-controller" - "ack-s3-controller" - "ack-sagemaker-controller" - "ack-secretsmanager-controller" - "ack-sfn-controller" - "ack-sns-controller" - "ack-sqs-controller" - "aerospike-kubernetes-operator" - "airflow-helm-operator" - "aiven-operator" - "akka-cluster-operator" - "alvearie-imaging-ingestion" - "anchore-engine" - "apch-operator" - "api-operator" - "api-testing-operator" - "apicast-community-operator" - "apicurio-registry" - "apimatic-kubernetes-operator" - "app-director-operator" - "appdynamics-operator" - "application-services-metering-operator" - "appranix" - "aqua" - "argocd-operator" - ... - ``` - - !!! important - Currently, OLM 1.0 does not support the installation of extensions that use webhooks or that target a single or specified set of namespaces. - - * Return list of packages that support `AllNamespaces` install mode and do not use webhooks: - - ``` terminal - curl -k https://localhost:8443/catalogs/operatorhubio/all.json | jq -c 'select(.schema == "olm.bundle") | {"package":.package, "version":.properties[] | select(.type == "olm.bundle.object").value.data | @base64d | fromjson | select(.kind == "ClusterServiceVersion" and (.spec.installModes[] | select(.type == "AllNamespaces" and .supported == true) != null) and .spec.webhookdefinitions == null).spec.version}' - ``` - - ??? success - ``` text title="Example output" - {"package":"ack-acm-controller","version":"0.0.12"} - {"package":"ack-acmpca-controller","version":"0.0.5"} - {"package":"ack-apigatewayv2-controller","version":"1.0.7"} - {"package":"ack-applicationautoscaling-controller","version":"1.0.11"} - {"package":"ack-cloudfront-controller","version":"0.0.9"} - {"package":"ack-cloudtrail-controller","version":"1.0.8"} - {"package":"ack-cloudwatch-controller","version":"0.0.3"} - {"package":"ack-cloudwatchlogs-controller","version":"0.0.4"} - {"package":"ack-dynamodb-controller","version":"1.2.9"} - {"package":"ack-ec2-controller","version":"1.2.4"} - {"package":"ack-ecr-controller","version":"1.0.12"} - {"package":"ack-ecs-controller","version":"0.0.4"} - {"package":"ack-efs-controller","version":"0.0.5"} - {"package":"ack-eks-controller","version":"1.3.3"} - {"package":"ack-elasticache-controller","version":"0.0.29"} - {"package":"ack-emrcontainers-controller","version":"1.0.8"} - {"package":"ack-eventbridge-controller","version":"1.0.6"} - {"package":"ack-iam-controller","version":"1.3.6"} - {"package":"ack-kafka-controller","version":"0.0.4"} - {"package":"ack-keyspaces-controller","version":"0.0.11"} - ... - ``` - -3. Inspect the contents of an extension's metadata: - - ``` terminal - curl -k https://localhost:8443/catalogs/operatorhubio/all.json | jq -s '.[] | select( .schema == "olm.package") | select( .name == "")' - ``` - - `package_name` - : Specifies the name of the package you want to inspect. - - ??? success - ``` text title="Example output" - { - "defaultChannel": "stable-v6.x", - "icon": { - "base64data": "PHN2ZyB4bWxucz0ia... - "mediatype": "image/svg+xml" - }, - "name": "cockroachdb", - "schema": "olm.package" - } - ``` - -### Additional resources - -* [Catalog queries](../refs/catalog-queries.md) diff --git a/docs/Tasks/installing-an-extension.md b/docs/Tasks/installing-an-extension.md deleted file mode 100644 index 81a687042a..0000000000 --- a/docs/Tasks/installing-an-extension.md +++ /dev/null @@ -1,119 +0,0 @@ -# Installing an extension from a catalog - -In Operator Lifecycle Manager (OLM) 1.0, Kubernetes extensions are scoped to the cluster. -After you add a catalog to your cluster, you can install an extension by creating a custom resource (CR) and applying it. - -## Prerequisites - -* A deployed and unpacked catalog -* The name, and optionally version, or channel, of the extension to be installed -* The extension must be compatible with OLM 1.0 (see [current OLM v1 limitations](../drafts/refs/olmv1-limitations.md)) -* An existing namespace in which to install the extension -* A suitable service account for installation (more information can be found [here](../drafts/Tasks/create-installer-service-account.md)) - -## Procedure - -1. Create a CR for the Kubernetes extension you want to install: - - ``` yaml title="Example CR" - apiVersion: olm.operatorframework.io/v1alpha1 - kind: ClusterExtension - metadata: - name: - spec: - source: - sourceType: Catalog - catalog: - packageName: - channel: - version: "" - install: - namespace: - serviceAccount: - name: - ``` - - `extension_name` - : Specifies a custom name for the Kubernetes extension you want to install, such as `my-camel-k`. - - `package_name` - : Specifies the name of the package you want to install, such as `camel-k`. - - `channel` - : Optional: Specifies the extension's channel, such as `stable` or `candidate`. - - `version` - : Optional: Specifies the version or version range you want installed, such as `1.3.1` or `"<2"`. - If you use a comparison string to define a version range, the string must be surrounded by double quotes (`"`). - - `namespace_name` - : Specifies a name for the namespace in which the bundle of content for the package referenced - in the packageName field will be applied. - - `serviceAccount_name` - : serviceAccount name is a required reference to a ServiceAccount that exists - in the installNamespace. The provided ServiceAccount is used to install and - manage the content for the package specified in the packageName field. - - !!! warning - Currently, the following limitations affect the installation of extensions: - - * If multiple catalogs are added to a cluster, you cannot specify a catalog when you install an extension. - * OLM 1.0 requires that all of the extensions have unique bundle and package names for dependency resolution. - - As a result, if two catalogs have an extension with the same name, the installation might fail or lead to an unintended outcome. - For example, the first extension that matches might install successfully and finish without searching for a match in the second catalog. - -2. Apply the CR to the cluster: - - ``` terminal - kubectl apply -f .yaml - ``` - - ??? success - ``` text title="Example output" - clusterextension.olm.operatorframework.io/camel-k created - ``` - -### Verification - -* Describe the installed extension: - - ``` terminal - kubectl describe clusterextensions - ``` - - ??? success - ``` text title="Example output" - Name: my-camel-k - Namespace: - Labels: - Annotations: - API Version: olm.operatorframework.io/v1alpha1 - Kind: ClusterExtension - Metadata: - Creation Timestamp: 2024-03-15T15:03:47Z - Generation: 1 - Resource Version: 7691 - UID: d756879f-217d-4ebe-85b1-8427bbb2f1df - Spec: - Package Name: camel-k - Upgrade Constraint Policy: Enforce - Status: - Conditions: - Last Transition Time: 2024-03-15T15:03:50Z - Message: resolved to "quay.io/operatorhubio/camel-k@sha256:d2b74c43ec8f9294450c9dcf2057be328d0998bb924ad036db489af79d1b39c3" - Observed Generation: 1 - Reason: Success - Status: True - Type: Resolved - Last Transition Time: 2024-03-15T15:04:13Z - Message: installed from "quay.io/operatorhubio/camel-k@sha256:d2b74c43ec8f9294450c9dcf2057be328d0998bb924ad036db489af79d1b39c3" - Observed Generation: 1 - Reason: Success - Status: True - Type: Installed - Installed Bundle Resource: quay.io/operatorhubio/camel-k@sha256:d2b74c43ec8f9294450c9dcf2057be328d0998bb924ad036db489af79d1b39c3 - Resolved Bundle Resource: quay.io/operatorhubio/camel-k@sha256:d2b74c43ec8f9294450c9dcf2057be328d0998bb924ad036db489af79d1b39c3 - Events: - ``` diff --git a/docs/api-reference/catalogd-webserver.md b/docs/api-reference/catalogd-webserver.md new file mode 100644 index 0000000000..dc0b9bb0a8 --- /dev/null +++ b/docs/api-reference/catalogd-webserver.md @@ -0,0 +1,91 @@ +# Catalogd web server + +[Catalogd](https://github.com/operator-framework/operator-controller/tree/main/catalogd), the OLM v1 component for making catalog contents available on cluster, includes +a web server that serves catalog contents to clients via an HTTP(S) endpoint. + +The endpoint to retrieve this information can be composed from the `.status.urls.base` of a `ClusterCatalog` resource with the selected access API path. +As an example, to access the full FBC via the v1 API endpoint (indicated by path `api/v1/all`) where `.status.urls.base` is + +```yaml + urls: + base: https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio +``` + +the URL to access the service would be `https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio/api/v1/all` + +!!! note + + The values of the `.status.urls` field in a `ClusterCatalog` resource are arbitrary string values and can change at any time. + While there are no guarantees on the exact value of this field, it will always contain catalog-specific API endpoints for use + by clients to make a request from within the cluster. + +## Interacting With the Server + +### Supported HTTP Methods + +The HTTP request methods supported by the catalogd web server are: + +- [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) +- [HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) + +### Response Format + +Responses are encoded as a [JSON Lines](https://jsonlines.org/) stream of [File-Based Catalog](https://olm.operatorframework.io/docs/reference/file-based-catalogs) (FBC) [Meta](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#schema) objects delimited by newlines. + +??? example "Example JSON-encoded FBC snippet" + + ```json + { + "schema": "olm.package", + "name": "cockroachdb", + "defaultChannel": "stable-v6.x", + } + { + "schema": "olm.channel", + "name": "stable-v6.x", + "package": "cockroachdb", + "entries": [ + { + "name": "cockroachdb.v6.0.0", + "skipRange": "<6.0.0" + } + ] + } + { + "schema": "olm.bundle", + "name": "cockroachdb.v6.0.0", + "package": "cockroachdb", + "image": "quay.io/openshift-community-operators/cockroachdb@sha256:d3016b1507515fc7712f9c47fd9082baf9ccb070aaab58ed0ef6e5abdedde8ba", + "properties": [ + { + "type": "olm.package", + "value": { + "packageName": "cockroachdb", + "version": "6.0.0" + } + }, + ], + } + ``` + + Corresponding JSON lines response: + ```jsonlines + {"schema":"olm.package","name":"cockroachdb","defaultChannel":"stable-v6.x"} + {"schema":"olm.channel","name":"stable-v6.x","package":"cockroachdb","entries":[{"name":"cockroachdb.v6.0.0","skipRange":"<6.0.0"}]} + {"schema":"olm.bundle","name":"cockroachdb.v6.0.0","package":"cockroachdb","image":"quay.io/openshift-community-operators/cockroachdb@sha256:d3016b1507515fc7712f9c47fd9082baf9ccb070aaab58ed0ef6e5abdedde8ba","properties":[{"type":"olm.package","value":{"packageName":"cockroachdb","version":"6.0.0"}}]} + ``` + +### Compression Support + +The `catalogd` web server supports gzip compression of responses, which can significantly reduce associated network traffic. In order to signal that the client handles compressed responses, the client must include `Accept-Encoding: gzip` as a header in the HTTP request. + +The web server will include a `Content-Encoding: gzip` header in compressed responses. + +!!! note + + Only catalogs whose uncompressed response body would result in a response size greater than 1400 bytes will be compressed. + +### Cache Header Support + +For clients interested in caching the information returned from the `catalogd` web server, the `Last-Modified` header is set +on responses and the `If-Modified-Since` header is supported for requests. diff --git a/docs/api-reference/crd-ref-docs-gen-config.yaml b/docs/api-reference/crd-ref-docs-gen-config.yaml new file mode 100644 index 0000000000..c8efa15c19 --- /dev/null +++ b/docs/api-reference/crd-ref-docs-gen-config.yaml @@ -0,0 +1,7 @@ +processor: + ignoreTypes: [ClusterExtensionRevision, ClusterExtensionRevisionList] + ignoreFields: [] + +render: + # Version of Kubernetes to use when generating links to Kubernetes API documentation. + kubernetesVersion: 1.31 diff --git a/docs/api-reference/olmv1-api-reference.md b/docs/api-reference/olmv1-api-reference.md new file mode 100644 index 0000000000..b21e404520 --- /dev/null +++ b/docs/api-reference/olmv1-api-reference.md @@ -0,0 +1,505 @@ +# API Reference + +## Packages +- [olm.operatorframework.io/v1](#olmoperatorframeworkiov1) + + +## olm.operatorframework.io/v1 + +Package v1 contains API Schema definitions for the olm v1 API group + +### Resource Types +- [ClusterCatalog](#clustercatalog) +- [ClusterCatalogList](#clustercataloglist) +- [ClusterExtension](#clusterextension) +- [ClusterExtensionList](#clusterextensionlist) + + + +#### AvailabilityMode + +_Underlying type:_ _string_ + +AvailabilityMode defines the availability of the catalog + + + +_Appears in:_ +- [ClusterCatalogSpec](#clustercatalogspec) + +| Field | Description | +| --- | --- | +| `Available` | | +| `Unavailable` | | + + +#### BundleMetadata + + + +BundleMetadata is a representation of the identifying attributes of a bundle. + + + +_Appears in:_ +- [ClusterExtensionInstallStatus](#clusterextensioninstallstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is required and follows the DNS subdomain standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters,
hyphens (-) or periods (.), start and end with an alphanumeric character,
and be no longer than 253 characters. | | Required: \{\}
| +| `version` _string_ | version is a required field and is a reference to the version that this bundle represents
version follows the semantic versioning standard as defined in https://semver.org/. | | Required: \{\}
| + + +#### CRDUpgradeSafetyEnforcement + +_Underlying type:_ _string_ + + + + + +_Appears in:_ +- [CRDUpgradeSafetyPreflightConfig](#crdupgradesafetypreflightconfig) + +| Field | Description | +| --- | --- | +| `None` | None will not perform CRD upgrade safety checks.
| +| `Strict` | Strict will enforce the CRD upgrade safety check and block the upgrade if the CRD would not pass the check.
| + + +#### CRDUpgradeSafetyPreflightConfig + + + +CRDUpgradeSafetyPreflightConfig is the configuration for CRD upgrade safety preflight check. + + + +_Appears in:_ +- [PreflightConfig](#preflightconfig) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `enforcement` _[CRDUpgradeSafetyEnforcement](#crdupgradesafetyenforcement)_ | enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check.

Allowed values are "None" or "Strict". The default value is "Strict".

When set to "None", the CRD Upgrade Safety pre-flight check will be skipped
when performing an upgrade operation. This should be used with caution as
unintended consequences such as data loss can occur.

When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when
performing an upgrade operation. | | Enum: [None Strict]
Required: \{\}
| + + +#### CatalogFilter + + + +CatalogFilter defines the attributes used to identify and filter content from a catalog. + + + +_Appears in:_ +- [SourceConfig](#sourceconfig) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `packageName` _string_ | packageName is a reference to the name of the package to be installed
and is used to filter the content from catalogs.

packageName is required, immutable, and follows the DNS subdomain standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters,
hyphens (-) or periods (.), start and end with an alphanumeric character,
and be no longer than 253 characters.

Some examples of valid values are:
- some-package
- 123-package
- 1-package-2
- somepackage

Some examples of invalid values are:
- -some-package
- some-package-
- thisisareallylongpackagenamethatisgreaterthanthemaximumlength
- some.package

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253
Required: \{\}
| +| `version` _string_ | version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed.

Acceptable version ranges are no longer than 64 characters.
Version ranges are composed of comma- or space-delimited values and one or
more comparison operators, known as comparison strings. Additional
comparison strings can be added using the OR operator (\|\|).

# Range Comparisons

To specify a version range, you can use a comparison string like ">=3.0,
<3.6". When specifying a range, automatic updates will occur within that
range. The example comparison string means "install any version greater than
or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any
upgrades are available within the version range after initial installation,
those upgrades should be automatically performed.

# Pinned Versions

To specify an exact version to install you can use a version range that
"pins" to a specific version. When pinning to a specific version, no
automatic updates will occur. An example of a pinned version range is
"0.6.0", which means "only install version 0.6.0 and never
upgrade from this version".

# Basic Comparison Operators

The basic comparison operators and their meanings are:
- "=", equal (not aliased to an operator)
- "!=", not equal
- "<", less than
- ">", greater than
- ">=", greater than OR equal to
- "<=", less than OR equal to

# Wildcard Comparisons

You can use the "x", "X", and "*" characters as wildcard characters in all
comparison operations. Some examples of using the wildcard characters:
- "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0"
- ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0"
- "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3"
- "x", "X", and "*" is equivalent to ">= 0.0.0"

# Patch Release Comparisons

When you want to specify a minor version up to the next major version you
can use the "~" character to perform patch comparisons. Some examples:
- "~1.2.3" is equivalent to ">=1.2.3, <1.3.0"
- "~1" and "~1.x" is equivalent to ">=1, <2"
- "~2.3" is equivalent to ">=2.3, <2.4"
- "~1.2.x" is equivalent to ">=1.2.0, <1.3.0"

# Major Release Comparisons

You can use the "^" character to make major release comparisons after a
stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples:
- "^1.2.3" is equivalent to ">=1.2.3, <2.0.0"
- "^1.2.x" is equivalent to ">=1.2.0, <2.0.0"
- "^2.3" is equivalent to ">=2.3, <3"
- "^2.x" is equivalent to ">=2.0.0, <3"
- "^0.2.3" is equivalent to ">=0.2.3, <0.3.0"
- "^0.2" is equivalent to ">=0.2.0, <0.3.0"
- "^0.0.3" is equvalent to ">=0.0.3, <0.0.4"
- "^0.0" is equivalent to ">=0.0.0, <0.1.0"
- "^0" is equivalent to ">=0.0.0, <1.0.0"

# OR Comparisons
You can use the "\|\|" character to represent an OR operation in the version
range. Some examples:
- ">=1.2.3, <2.0.0 \|\| >3.0.0"
- "^0 \|\| ^3 \|\| ^5"

For more information on semver, please see https://semver.org/ | | MaxLength: 64
| +| `channels` _string array_ | channels is an optional reference to a set of channels belonging to
the package specified in the packageName field.

A "channel" is a package-author-defined stream of updates for an extension.

Each channel in the list must follow the DNS subdomain standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters,
hyphens (-) or periods (.), start and end with an alphanumeric character,
and be no longer than 253 characters. No more than 256 channels can be specified.

When specified, it is used to constrain the set of installable bundles and
the automated upgrade path. This constraint is an AND operation with the
version field. For example:
- Given channel is set to "foo"
- Given version is set to ">=1.0.0, <1.5.0"
- Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable
- Automatic upgrades will be constrained to upgrade edges defined by the selected channel

When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths.

Some examples of valid values are:
- 1.1.x
- alpha
- stable
- stable-v1
- v1-stable
- dev-preview
- preview
- community

Some examples of invalid values are:
- -some-channel
- some-channel-
- thisisareallylongchannelnamethatisgreaterthanthemaximumlength
- original_40
- --default-channel

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxItems: 256
| +| `selector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#labelselector-v1-meta)_ | selector is an optional field that can be used
to filter the set of ClusterCatalogs used in the bundle
selection process.

When unspecified, all ClusterCatalogs will be used in
the bundle selection process. | | | +| `upgradeConstraintPolicy` _[UpgradeConstraintPolicy](#upgradeconstraintpolicy)_ | upgradeConstraintPolicy is an optional field that controls whether
the upgrade path(s) defined in the catalog are enforced for the package
referenced in the packageName field.

Allowed values are: "CatalogProvided" or "SelfCertified", or omitted.

When this field is set to "CatalogProvided", automatic upgrades will only occur
when upgrade constraints specified by the package author are met.

When this field is set to "SelfCertified", the upgrade constraints specified by
the package author are ignored. This allows for upgrades and downgrades to
any version of the package. This is considered a dangerous operation as it
can lead to unknown and potentially disastrous outcomes, such as data
loss. It is assumed that users have independently verified changes when
using this option.

When this field is omitted, the default value is "CatalogProvided". | CatalogProvided | Enum: [CatalogProvided SelfCertified]
| + + +#### CatalogSource + + + +CatalogSource is a discriminated union of possible sources for a Catalog. +CatalogSource contains the sourcing information for a Catalog + + + +_Appears in:_ +- [ClusterCatalogSpec](#clustercatalogspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `type` _[SourceType](#sourcetype)_ | type is a reference to the type of source the catalog is sourced from.
type is required.

The only allowed value is "Image".

When set to "Image", the ClusterCatalog content will be sourced from an OCI image.
When using an image source, the image field must be set and must be the only field defined for this type. | | Enum: [Image]
Required: \{\}
| +| `image` _[ImageSource](#imagesource)_ | image is used to configure how catalog contents are sourced from an OCI image.
This field is required when type is Image, and forbidden otherwise. | | | + + +#### ClusterCatalog + + + +ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. +For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + + + +_Appears in:_ +- [ClusterCatalogList](#clustercataloglist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterCatalog` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[ClusterCatalogSpec](#clustercatalogspec)_ | spec is the desired state of the ClusterCatalog.
spec is required.
The controller will work to ensure that the desired
catalog is unpacked and served over the catalog content HTTP server. | | Required: \{\}
| +| `status` _[ClusterCatalogStatus](#clustercatalogstatus)_ | status contains information about the state of the ClusterCatalog such as:
- Whether or not the catalog contents are being served via the catalog content HTTP server
- Whether or not the ClusterCatalog is progressing to a new state
- A reference to the source from which the catalog contents were retrieved | | | + + +#### ClusterCatalogList + + + +ClusterCatalogList contains a list of ClusterCatalog + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterCatalogList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[ClusterCatalog](#clustercatalog) array_ | items is a list of ClusterCatalogs.
items is required. | | Required: \{\}
| + + +#### ClusterCatalogSpec + + + +ClusterCatalogSpec defines the desired state of ClusterCatalog + + + +_Appears in:_ +- [ClusterCatalog](#clustercatalog) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `source` _[CatalogSource](#catalogsource)_ | source allows a user to define the source of a catalog.
A "catalog" contains information on content that can be installed on a cluster.
Providing a catalog source makes the contents of the catalog discoverable and usable by
other on-cluster components.
These on-cluster components may do a variety of things with this information, such as
presenting the content in a GUI dashboard or installing content from the catalog on the cluster.
The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format.
For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs.
source is a required field.

Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image:

source:
type: Image
image:
ref: quay.io/operatorhubio/catalog:latest | | Required: \{\}
| +| `priority` _integer_ | priority allows the user to define a priority for a ClusterCatalog.
priority is optional.

A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements.
A higher number means higher priority.

It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements.
When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input.

When omitted, the default priority is 0 because that is the zero value of integers.

Negative numbers can be used to specify a priority lower than the default.
Positive numbers can be used to specify a priority higher than the default.

The lowest possible value is -2147483648.
The highest possible value is 2147483647. | 0 | | +| `availabilityMode` _[AvailabilityMode](#availabilitymode)_ | availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster.
availabilityMode is optional.

Allowed values are "Available" and "Unavailable" and omitted.

When omitted, the default value is "Available".

When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server.
Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog
and its contents as usable.

When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server.
When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing.
Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want
to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. | Available | Enum: [Unavailable Available]
| + + +#### ClusterCatalogStatus + + + +ClusterCatalogStatus defines the observed state of ClusterCatalog + + + +_Appears in:_ +- [ClusterCatalog](#clustercatalog) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | conditions is a representation of the current state for this ClusterCatalog.

The current condition types are Serving and Progressing.

The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server.
When it has a status of True and a reason of Available, the contents of the catalog are being served.
When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available.
When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable.

The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state.
When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts.
When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing.
When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery.

In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched
catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog
contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes
to the contents we identify that there are updates to the contents. | | | +| `resolvedSource` _[ResolvedCatalogSource](#resolvedcatalogsource)_ | resolvedSource contains information about the resolved source based on the source type. | | | +| `urls` _[ClusterCatalogURLs](#clustercatalogurls)_ | urls contains the URLs that can be used to access the catalog. | | | +| `lastUnpacked` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#time-v1-meta)_ | lastUnpacked represents the last time the contents of the
catalog were extracted from their source format. As an example,
when using an Image source, the OCI image will be pulled and the
image layers written to a file-system backed cache. We refer to the
act of this extraction from the source format as "unpacking". | | | + + +#### ClusterCatalogURLs + + + +ClusterCatalogURLs contains the URLs that can be used to access the catalog. + + + +_Appears in:_ +- [ClusterCatalogStatus](#clustercatalogstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `base` _string_ | base is a cluster-internal URL that provides endpoints for
accessing the content of the catalog.

It is expected that clients append the path for the endpoint they wish
to access.

Currently, only a single endpoint is served and is accessible at the path
/api/v1.

The endpoints served for the v1 API are:
- /all - this endpoint returns the entirety of the catalog contents in the FBC format

As the needs of users and clients of the evolve, new endpoints may be added. | | MaxLength: 525
Required: \{\}
| + + +#### ClusterExtension + + + +ClusterExtension is the Schema for the clusterextensions API + + + +_Appears in:_ +- [ClusterExtensionList](#clusterextensionlist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterExtension` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[ClusterExtensionSpec](#clusterextensionspec)_ | spec is an optional field that defines the desired state of the ClusterExtension. | | | +| `status` _[ClusterExtensionStatus](#clusterextensionstatus)_ | status is an optional field that defines the observed state of the ClusterExtension. | | | + + +#### ClusterExtensionConfig + + + +ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into +the ClusterExtension's rendered manifests. + + + +_Appears in:_ +- [ClusterExtensionSpec](#clusterextensionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `configType` _[ClusterExtensionConfigType](#clusterextensionconfigtype)_ | configType is a required reference to the type of configuration source.

Allowed values are "Inline"

When this field is set to "Inline", the cluster extension configuration is defined inline within the
ClusterExtension resource. | | Enum: [Inline]
Required: \{\}
| +| `inline` _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#json-v1-apiextensions-k8s-io)_ | inline contains JSON or YAML values specified directly in the
ClusterExtension.

inline must be set if configType is 'Inline'.
inline accepts arbitrary JSON/YAML objects.
inline is validation at runtime against the schema provided by the bundle if a schema is provided. | | Type: object
| + + +#### ClusterExtensionConfigType + +_Underlying type:_ _string_ + + + + + +_Appears in:_ +- [ClusterExtensionConfig](#clusterextensionconfig) + +| Field | Description | +| --- | --- | +| `Inline` | | + + +#### ClusterExtensionInstallConfig + + + +ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config. +ClusterExtensionInstallConfig requires the namespace and serviceAccount which should be used for the installation of packages. + + + +_Appears in:_ +- [ClusterExtensionSpec](#clusterextensionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `preflight` _[PreflightConfig](#preflightconfig)_ | preflight is an optional field that can be used to configure the checks that are
run before installation or upgrade of the content for the package specified in the packageName field.

When specified, it replaces the default preflight configuration for install/upgrade actions.
When not specified, the default configuration will be used. | | | + + +#### ClusterExtensionInstallStatus + + + +ClusterExtensionInstallStatus is a representation of the status of the identified bundle. + + + +_Appears in:_ +- [ClusterExtensionStatus](#clusterextensionstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `bundle` _[BundleMetadata](#bundlemetadata)_ | bundle is a required field which represents the identifying attributes of a bundle.

A "bundle" is a versioned set of content that represents the resources that
need to be applied to a cluster to install a package. | | Required: \{\}
| + + +#### ClusterExtensionList + + + +ClusterExtensionList contains a list of ClusterExtension + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `olm.operatorframework.io/v1` | | | +| `kind` _string_ | `ClusterExtensionList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[ClusterExtension](#clusterextension) array_ | items is a required list of ClusterExtension objects. | | Required: \{\}
| + + +#### ClusterExtensionSpec + + + +ClusterExtensionSpec defines the desired state of ClusterExtension + + + +_Appears in:_ +- [ClusterExtension](#clusterextension) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `namespace` _string_ | namespace is a reference to a Kubernetes namespace.
This is the namespace in which the provided ServiceAccount must exist.
It also designates the default namespace where namespace-scoped resources
for the extension are applied to the cluster.
Some extensions may contain namespace-scoped resources to be applied in other namespaces.
This namespace must exist.

namespace is required, immutable, and follows the DNS label standard
as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-),
start and end with an alphanumeric character, and be no longer than 63 characters

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63
Required: \{\}
| +| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions
with the cluster that are required to manage the extension.
The ServiceAccount must be configured with the necessary permissions to perform these interactions.
The ServiceAccount must exist in the namespace referenced in the spec.
serviceAccount is required. | | Required: \{\}
| +| `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content
for this ClusterExtension. Selection is performed by setting the sourceType.

Catalog is currently the only implemented sourceType, and setting the
sourcetype to "Catalog" requires the catalog field to also be defined.

Below is a minimal example of a source definition (in yaml):

source:
sourceType: Catalog
catalog:
packageName: example-package | | Required: \{\}
| +| `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is an optional field used to configure the installation options
for the ClusterExtension such as the pre-flight check configuration. | | | +| `config` _[ClusterExtensionConfig](#clusterextensionconfig)_ | config is an optional field used to specify bundle specific configuration
used to configure the bundle. Configuration is bundle specific and a bundle may provide
a configuration schema. When not specified, the default configuration of the resolved bundle will be used.

config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide
a configuration schema the final manifests will be derived on a best-effort basis. More information on how
to configure the bundle should be found in its end-user documentation.

| | | + + +#### ClusterExtensionStatus + + + +ClusterExtensionStatus defines the observed state of a ClusterExtension. + + + +_Appears in:_ +- [ClusterExtension](#clusterextension) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | The set of condition types which apply to all spec.source variations are Installed and Progressing.

The Installed condition represents whether or not the bundle has been installed for this ClusterExtension.
When Installed is True and the Reason is Succeeded, the bundle has been successfully installed.
When Installed is False and the Reason is Failed, the bundle has failed to install.

The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state.
When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state.
When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts.
When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery.

When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition.
These are indications from a package owner to guide users away from a particular package, channel, or bundle.
BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog.
ChannelDeprecated is set if the requested channel is marked deprecated in the catalog.
PackageDeprecated is set if the requested package is marked deprecated in the catalog.
Deprecated is a rollup condition that is present when any of the deprecated conditions are present. | | | +| `install` _[ClusterExtensionInstallStatus](#clusterextensioninstallstatus)_ | install is a representation of the current installation status for this ClusterExtension. | | | + + + + +#### ImageSource + + + +ImageSource enables users to define the information required for sourcing a Catalog from an OCI image + + +If we see that there is a possibly valid digest-based image reference AND pollIntervalMinutes is specified, +reject the resource since there is no use in polling a digest-based image reference. + + + +_Appears in:_ +- [CatalogSource](#catalogsource) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ref` _string_ | ref allows users to define the reference to a container image containing Catalog contents.
ref is required.
ref can not be more than 1000 characters.

A reference can be broken down into 3 parts - the domain, name, and identifier.

The domain is typically the registry where an image is located.
It must be alphanumeric characters (lowercase and uppercase) separated by the "." character.
Hyphenation is allowed, but the domain must start and end with alphanumeric characters.
Specifying a port to use is also allowed by adding the ":" character followed by numeric values.
The port must be the last value in the domain.
Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080".

The name is typically the repository in the registry where an image is located.
It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters.
Multiple names can be concatenated with the "/" character.
The domain and name are combined using the "/" character.
Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod".
An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog".

The identifier is typically the tag or digest for an image reference and is present at the end of the reference.
It starts with a separator character used to distinguish the end of the name and beginning of the identifier.
For a digest-based reference, the "@" character is the separator.
For a tag-based reference, the ":" character is the separator.
An identifier is required in the reference.

Digest-based references must contain an algorithm reference immediately after the "@" separator.
The algorithm reference must be followed by the ":" character and an encoded string.
The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters.
Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58".
The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters.

Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters.
The tag must not be longer than 127 characters.

An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05"
An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" | | MaxLength: 1000
Required: \{\}
| +| `pollIntervalMinutes` _integer_ | pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content.
pollIntervalMinutes is optional.
pollIntervalMinutes can not be specified when ref is a digest-based reference.

When omitted, the image will not be polled for new content. | | Minimum: 1
| + + +#### PreflightConfig + + + +PreflightConfig holds the configuration for the preflight checks. If used, at least one preflight check must be non-nil. + + + +_Appears in:_ +- [ClusterExtensionInstallConfig](#clusterextensioninstallconfig) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `crdUpgradeSafety` _[CRDUpgradeSafetyPreflightConfig](#crdupgradesafetypreflightconfig)_ | crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight
checks that run prior to upgrades of installed content.

The CRD Upgrade Safety pre-flight check safeguards from unintended
consequences of upgrading a CRD, such as data loss. | | | + + +#### ResolvedCatalogSource + + + +ResolvedCatalogSource is a discriminated union of resolution information for a Catalog. +ResolvedCatalogSource contains the information about a sourced Catalog + + + +_Appears in:_ +- [ClusterCatalogStatus](#clustercatalogstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `type` _[SourceType](#sourcetype)_ | type is a reference to the type of source the catalog is sourced from.
type is required.

The only allowed value is "Image".

When set to "Image", information about the resolved image source will be set in the 'image' field. | | Enum: [Image]
Required: \{\}
| +| `image` _[ResolvedImageSource](#resolvedimagesource)_ | image is a field containing resolution information for a catalog sourced from an image.
This field must be set when type is Image, and forbidden otherwise. | | | + + +#### ResolvedImageSource + + + +ResolvedImageSource provides information about the resolved source of a Catalog sourced from an image. + + + +_Appears in:_ +- [ResolvedCatalogSource](#resolvedcatalogsource) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ref` _string_ | ref contains the resolved image digest-based reference.
The digest format is used so users can use other tooling to fetch the exact
OCI manifests that were used to extract the catalog contents. | | MaxLength: 1000
Required: \{\}
| + + +#### ServiceAccountReference + + + +ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension. + + + +_Appears in:_ +- [ClusterExtensionSpec](#clusterextensionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a required, immutable reference to the name of the ServiceAccount
to be used for installation and management of the content for the package
specified in the packageName field.

This ServiceAccount must exist in the installNamespace.

name follows the DNS subdomain standard as defined in [RFC 1123].
It must contain only lowercase alphanumeric characters,
hyphens (-) or periods (.), start and end with an alphanumeric character,
and be no longer than 253 characters.

Some examples of valid values are:
- some-serviceaccount
- 123-serviceaccount
- 1-serviceaccount-2
- someserviceaccount
- some.serviceaccount

Some examples of invalid values are:
- -some-serviceaccount
- some-serviceaccount-

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253
Required: \{\}
| + + +#### SourceConfig + + + +SourceConfig is a discriminated union which selects the installation source. + + + +_Appears in:_ +- [ClusterExtensionSpec](#clusterextensionspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `sourceType` _string_ | sourceType is a required reference to the type of install source.

Allowed values are "Catalog"

When this field is set to "Catalog", information for determining the
appropriate bundle of content to install will be fetched from
ClusterCatalog resources existing on the cluster.
When using the Catalog sourceType, the catalog field must also be set. | | Enum: [Catalog]
Required: \{\}
| +| `catalog` _[CatalogFilter](#catalogfilter)_ | catalog is used to configure how information is sourced from a catalog.
This field is required when sourceType is "Catalog", and forbidden otherwise. | | | + + +#### SourceType + +_Underlying type:_ _string_ + +SourceType defines the type of source used for catalogs. + + + +_Appears in:_ +- [CatalogSource](#catalogsource) +- [ResolvedCatalogSource](#resolvedcatalogsource) + +| Field | Description | +| --- | --- | +| `Image` | | + + +#### UpgradeConstraintPolicy + +_Underlying type:_ _string_ + + + + + +_Appears in:_ +- [CatalogFilter](#catalogfilter) + +| Field | Description | +| --- | --- | +| `CatalogProvided` | The extension will only upgrade if the new version satisfies
the upgrade constraints set by the package author.
| +| `SelfCertified` | Unsafe option which allows an extension to be
upgraded or downgraded to any available version of the package and
ignore the upgrade path designed by package authors.
This assumes that users independently verify the outcome of the changes.
Use with caution as this can lead to unknown and potentially
disastrous results such as data loss.
| + + diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000000..3e62f8eb72 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,98 @@ + + + + +logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/components.md b/docs/components.md deleted file mode 100644 index 160e72117a..0000000000 --- a/docs/components.md +++ /dev/null @@ -1,5 +0,0 @@ -OLM v1 is composed of various component projects: - -* [operator-controller](https://github.com/operator-framework/operator-controller): operator-controller is the central component of OLM v1, that consumes all of the components below to extend Kubernetes to allows users to install, and manage the lifecycle of other extensions - -* [catalogD](https://github.com/operator-framework/catalogd): Catalogd is a Kubernetes extension that unpacks [file-based catalog (FBC)](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs) content that is packaged and shipped in container images, for consumption by clients on-clusters (unpacking from other sources, like git repos, OCI artifacts etc, are in the roadmap for catalogD). As component of the Operator Lifecycle Manager (OLM) v1 microservices architecture, catalogD hosts metadata for Kubernetes extensions packaged by the authors of the extensions, as a result helping customers discover installable content. diff --git a/docs/drafts/controlling-catalog-selection.md b/docs/concepts/controlling-catalog-selection.md similarity index 55% rename from docs/drafts/controlling-catalog-selection.md rename to docs/concepts/controlling-catalog-selection.md index e91a1eb0f1..384b7faf34 100644 --- a/docs/drafts/controlling-catalog-selection.md +++ b/docs/concepts/controlling-catalog-selection.md @@ -18,19 +18,24 @@ To select a specific catalog by name, you can use the `matchLabels` field in you #### Example ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: my-extension + name: argocd spec: - packageName: my-package - catalog: - selector: - matchLabels: - olm.operatorframework.io/metadata.name: my-catalog + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + selector: + matchLabels: + olm.operatorframework.io/metadata.name: operatorhubio ``` -In this example, only the catalog named `my-catalog` will be considered when resolving `my-package`. +In this example, only the catalog named `operatorhubio` will be considered when resolving `argocd-operator`. ### Selecting Catalogs by Labels @@ -39,16 +44,21 @@ If you have catalogs labeled with specific metadata, you can select them using ` #### Using `matchLabels` ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: my-extension + name: argocd spec: - packageName: my-package - catalog: - selector: - matchLabels: - example.com/support: "true" + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + selector: + matchLabels: + example.com/support: "true" ``` This selects catalogs labeled with `example.com/support: "true"`. @@ -56,20 +66,25 @@ This selects catalogs labeled with `example.com/support: "true"`. #### Using `matchExpressions` ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: my-extension + name: argocd spec: - packageName: my-package - catalog: - selector: - matchExpressions: - - key: example.com/support - operator: In - values: - - "gold" - - "platinum" + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + selector: + matchExpressions: + - key: example.com/support + operator: In + values: + - "gold" + - "platinum" ``` This selects catalogs where the label `example.com/support` has the value `gold` or `platinum`. @@ -81,19 +96,24 @@ You can exclude catalogs by using the `NotIn` or `DoesNotExist` operators in `ma #### Example: Exclude Specific Catalogs ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: my-extension + name: argocd spec: - packageName: my-package - catalog: - selector: - matchExpressions: - - key: olm.operatorframework.io/metadata.name - operator: NotIn - values: - - unwanted-catalog + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + selector: + matchExpressions: + - key: olm.operatorframework.io/metadata.name + operator: NotIn + values: + - unwanted-catalog ``` This excludes the catalog named `unwanted-catalog` from consideration. @@ -101,17 +121,22 @@ This excludes the catalog named `unwanted-catalog` from consideration. #### Example: Exclude Catalogs with a Specific Label ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: my-extension + name: argocd spec: - packageName: my-package - catalog: - selector: - matchExpressions: - - key: example.com/support - operator: DoesNotExist + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + selector: + matchExpressions: + - key: example.com/support + operator: DoesNotExist ``` This selects catalogs that do not have the `example.com/support` label. @@ -125,16 +150,16 @@ When multiple catalogs provide the same package, you can set priorities to resol In your `ClusterCatalog` resource, set the `priority` field: ```yaml -apiVersion: catalogd.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: name: high-priority-catalog spec: priority: 1000 source: - type: image + type: Image image: - ref: quay.io/example/high-priority-catalog:latest + ref: quay.io/example/high-priority-content-management:latest ``` Catalogs have a default priority of `0`. The priority can be any 32-bit integer. Catalogs with higher priority values are preferred during bundle resolution. @@ -159,70 +184,76 @@ If the system cannot resolve to a single bundle due to ambiguity, it will genera 1. **Create or Update `ClusterCatalogs` with Appropriate Labels and Priority** - ```yaml - apiVersion: catalogd.operatorframework.io/v1alpha1 - kind: ClusterCatalog - metadata: - name: catalog-a - labels: - example.com/support: "true" - spec: - priority: 1000 - source: - type: image - image: - ref: quay.io/example/catalog-a:latest - ``` - - ```yaml - apiVersion: catalogd.operatorframework.io/v1alpha1 - kind: ClusterCatalog - metadata: - name: catalog-b - labels: - example.com/support: "false" - spec: - priority: 500 - source: - type: image - image: - ref: quay.io/example/catalog-b:latest - ``` - NB: an `olm.operatorframework.io/metadata.name` label will be added automatically to ClusterCatalogs when applied + ```yaml + apiVersion: olm.operatorframework.io/v1 + kind: ClusterCatalog + metadata: + name: catalog-a + labels: + example.com/support: "true" + spec: + priority: 1000 + source: + type: Image + image: + ref: quay.io/example/content-management-a:latest + ``` + + ```yaml + apiVersion: olm.operatorframework.io/v1 + kind: ClusterCatalog + metadata: + name: catalog-b + labels: + example.com/support: "false" + spec: + priority: 500 + source: + type: Image + image: + ref: quay.io/example/content-management-b:latest + ``` + !!! note + An `olm.operatorframework.io/metadata.name` label will be added automatically to ClusterCatalogs when applied 2. **Create a `ClusterExtension` with Catalog Selection** - ```yaml - apiVersion: olm.operatorframework.io/v1alpha1 - kind: ClusterExtension - metadata: - name: install-my-operator - spec: - packageName: my-operator - catalog: - selector: - matchLabels: - example.com/support: "true" - ``` + ```yaml + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: install-my-operator + spec: + namespace: my-operator-ns + serviceAccount: + name: my-operator-installer + source: + sourceType: Catalog + catalog: + packageName: my-operator + selector: + matchLabels: + example.com/support: "true" + ``` 3. **Apply the Resources** - ```shell - kubectl apply -f catalog-a.yaml - kubectl apply -f catalog-b.yaml - kubectl apply -f install-my-operator.yaml - ``` + ```shell + kubectl apply -f content-management-a.yaml + kubectl apply -f content-management-b.yaml + kubectl apply -f install-my-operator.yaml + ``` 4. **Verify the Installation** - Check the status of the `ClusterExtension`: + Check the status of the `ClusterExtension`: - ```shell - kubectl get clusterextension install-my-operator -o yaml - ``` + ```shell + kubectl get clusterextension install-my-operator -o yaml + ``` - The status should indicate that the bundle was resolved from `catalog-a` due to the higher priority and matching label. + The status should indicate that the bundle was resolved from `catalog-a` due to the higher priority and matching label. ## Important Notes diff --git a/docs/refs/crd-upgrade-safety.md b/docs/concepts/crd-upgrade-safety.md similarity index 94% rename from docs/refs/crd-upgrade-safety.md rename to docs/concepts/crd-upgrade-safety.md index 47ad18d7ba..53aef8f69e 100644 --- a/docs/refs/crd-upgrade-safety.md +++ b/docs/concepts/crd-upgrade-safety.md @@ -53,26 +53,26 @@ not cause the CRD Upgrade Safety preflight check to halt the upgrade: ## Disabling CRD Upgrade Safety The CRD Upgrade Safety preflight check can be entirely disabled by adding the -`preflight.crdUpgradeSafety.disabled` field with a value of "true" to the ClusterExtension of the CRD. +`.spec.install.preflight.crdUpgradeSafety.enforcement` field with a value of `None` to the `ClusterExtension` of the CRD. ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: - name: clusterextension-sample + name: argocd spec: - source: - sourceType: Catalog - catalog: - packageName: argocd-operator - version: 0.6.0 - install: - namespace: default - serviceAccount: - name: argocd-installer - preflight: - crdUpgradeSafety: - disabled: true + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 + install: + preflight: + crdUpgradeSafety: + enforcement: None ``` You cannot disable individual field validators. If you disable the CRD Upgrade Safety preflight check, all field validators are disabled. diff --git a/docs/concepts/permission-model.md b/docs/concepts/permission-model.md new file mode 100644 index 0000000000..cc23b7c959 --- /dev/null +++ b/docs/concepts/permission-model.md @@ -0,0 +1,29 @@ +#### OLMv1 Permission Model + +Here we aim to describe the OLMv1 permission model. OLMv1 itself does not have cluster-wide admin permissions. Therefore, each cluster extension must specify a service account with sufficient permissions to install and manage it. While this service account is distinct from any service account defined in the bundle, it will need sufficient privileges to create and assign the required RBAC. Therefore, the cluster extension service account's privileges would be a superset of the privileges required by the service account in the bundle. + +To understand the permission model, lets see the scope of the the service accounts associated with ClusterExtension deployment: + +#### Service Account associated with the ClusterExtension CR + +1) The ClusterExtension CR defines a service account to deploy and manage the ClusterExtension lifecycle and can be derived using the [document](../howto/derive-service-account.md). It is specified in the ClusterExtension [yaml](../tutorials/install-extension#L71) while deploying a ClusterExtension. +2) The purpose of the service account specified in the ClusterExtension spec is to manage the cluster extension lifecycle. Its permissions are the cumulative of the permissions required for managing the cluster extension lifecycle and any RBAC that maybe included in the extension bundle. +3) Since the extension bundle contains its own RBAC, it means the ClusterExtension service account requires either: +- the same set of permissions that are defined in the RBAC that it is trying to create. +- bind/escalate verbs for RBAC, see https://kubernetes.io/docs/reference/access-authn-authz/rbac/#privilege-escalation-prevention-and-bootstrapping + +#### Service Account/(s) part of the Extension Bundle +1) The contents of the extension bundle may contain more service accounts and RBAC. +2) The OLMv1 operator-controller creates the service account/(s) defined as part of the extension bundle with the required RBAC for the controller business logic. + +##### Example: + +Lets consider deployment of the ArgoCD operator. The ClusterExtension ClusterResource specifies a service account as part of its spec, usually denoted as the ClusterExtension installer service account. +The ArgoCD operator specifies the `argocd-operator-controller-manager` [service account](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L1124) with necessary RBAC for the bundle resources and OLMv1 creates it as part of this extension bundle deployment. + +The extension bundle CSV contains the [permissions](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L1091) and [cluster permissions](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L872) allow the operator to manage and run the controller logic. These permissions are assigned to the `argocd-operator-controller-manager` service account when the operator bundle is deployed. + +OLM v1 will assign all the RBAC specified in the extension bundle to the above service account. +The ClusterExtension installer service account will need all the RBAC specified for the `argocd-operator-controller-manager` and additional RBAC for deploying the ClusterExtension. + +**Note**: The ClusterExtension permissions are not propogated to the deployment. The ClusterExtension service account and the bundle's service accounts have different purposes and naming conflicts between the two service accounts can lead to failure of ClusterExtension deployment. diff --git a/docs/concepts/single-owner-objects.md b/docs/concepts/single-owner-objects.md new file mode 100644 index 0000000000..0553f70a8b --- /dev/null +++ b/docs/concepts/single-owner-objects.md @@ -0,0 +1,34 @@ +# OLM Ownership Enforcement for `ClusterExtensions` + +In OLM, **a Kubernetes resource can only be owned by a single `ClusterExtension` at a time**. This ensures that resources within a Kubernetes cluster are managed consistently and prevents conflicts between multiple `ClusterExtensions` attempting to control the same resource. + +## Key Concept: Single Ownership + +The core principle enforced by OLM is that each resource can only have one `ClusterExtension` as its owner. This prevents overlapping or conflicting management by multiple `ClusterExtensions`, ensuring that each resource is uniquely associated with only one operator bundle. + +## Implications of Single Ownership + +### 1. Operator Bundles That Provide a CRD Can Only Be Installed Once + +Operator bundles provide `CustomResourceDefinitions` (CRDs), which are part of a `ClusterExtension`. This means a bundle can only be installed once in a cluster. Attempting to install another bundle that provides the same CRDs will result in a failure, as each custom resource can have only one `ClusterExtension` as its owner. + + +### 2. `ClusterExtensions` Cannot Share Objects + +OLM's single-owner policy means that **`ClusterExtensions` cannot share ownership of any resources**. If one `ClusterExtension` manages a specific resource (e.g., a `Deployment`, `CustomResourceDefinition`, or `Service`), another `ClusterExtension` cannot claim ownership of the same resource. Any attempt to do so will be blocked by the system. + +## Error Messages + +When a conflict occurs due to multiple `ClusterExtensions` attempting to manage the same resource, `operator-controller` will return a clear error message, indicating the ownership conflict. + +- **Example Error**: + ```plaintext + CustomResourceDefinition 'logfilemetricexporters.logging.kubernetes.io' already exists in namespace 'kubernetes-logging' and cannot be managed by operator-controller + ``` + +This error message signals that the resource is already being managed by another `ClusterExtension` and cannot be reassigned or "shared." + +## What This Means for You + +- **Uniqueness of Operator Bundles**: Ensure that operator bundles providing the same CRDs are not installed more than once. This can prevent potential installation failures due to ownership conflicts. +- **Avoid Resource Sharing**: If you need different `ClusterExtensions` to interact with similar resources, ensure they are managing separate resources. `ClusterExtensions` cannot jointly manage the same resource due to the single-owner enforcement. diff --git a/docs/drafts/upgrade-support.md b/docs/concepts/upgrade-support.md similarity index 70% rename from docs/drafts/upgrade-support.md rename to docs/concepts/upgrade-support.md index 367a57ec1b..8ad7d589ad 100644 --- a/docs/drafts/upgrade-support.md +++ b/docs/concepts/upgrade-support.md @@ -1,3 +1,8 @@ +--- +hide: + - toc +--- + # Upgrade support This document explains how OLM v1 handles upgrades. @@ -10,19 +15,21 @@ It also introduces an API to enable independently verified upgrades and downgrad When determining upgrade edges, also known as upgrade paths or upgrade constraints, for an installed cluster extension, Operator Lifecycle Manager (OLM) v1 supports [legacy OLM semantics](https://olm.operatorframework.io/docs/concepts/olm-architecture/operator-catalog/creating-an-update-graph/) by default. This support follows the behavior from legacy OLM, including `replaces`, `skips`, and `skipRange` directives, with a few noted differences. -By supporting legacy OLM semantics, OLM v1 now honors the upgrade graph from catalogs accurately. +By supporting legacy OLM semantics, OLM v1 honors the upgrade graph from catalogs accurately. + +If there are multiple possible successors, OLM v1 behavior differs in the following ways: -* If there are multiple possible successors, OLM v1 behavior differs in the following ways: - * In legacy OLM, the successor closest to the channel head is chosen. - * In OLM v1, the successor with the highest semantic version (semver) is chosen. -* Consider the following set of file-based catalog (FBC) channel entries: +* In legacy OLM, the successor closest to the channel head is chosen. +* In OLM v1, the successor with the highest semantic version (semver) is chosen. + +Consider the following set of file-based catalog (FBC) channel entries: ```yaml # ... - name: example.v3.0.0 skips: ["example.v2.0.0"] - name: example.v2.0.0 - skipRange: >=1.0.0 <2.0.0 + skipRange: ">=1.0.0 <2.0.0" ``` If `1.0.0` is installed, OLM v1 behavior differs in the following ways: @@ -32,21 +39,24 @@ If `1.0.0` is installed, OLM v1 behavior differs in the following ways: You can change the default behavior of the upgrade constraints by setting the `upgradeConstraintPolicy` parameter in your cluster extension's custom resource (CR). -``` yaml hl_lines="10" -apiVersion: olm.operatorframework.io/v1alpha1 +``` yaml hl_lines="14" +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: spec: - installNamespace: - packageName: + namespace: serviceAccount: name: - upgradeConstraintPolicy: SelfCertified - version: "" + source: + sourceType: Catalog + catalog: + packageName: + version: "" + upgradeConstraintPolicy: SelfCertified ``` -where setting the `upgradeConstraintPolicy` to: +Setting the `upgradeConstraintPolicy` to: `SelfCertified` : Does not limit the next version to the set of successors, and instead allows for any downgrade, sidegrade, or upgrade. @@ -58,8 +68,8 @@ where setting the `upgradeConstraintPolicy` to: OLM supports Semver to provide a simplified way for package authors to define compatible upgrades. According to the Semver standard, releases within a major version (e.g. `>=1.0.0 <2.0.0`) must be compatible. As a result, package authors can publish a new package version following the Semver specification, and OLM assumes compatibility. Package authors do not have to explicitly define upgrade edges in the catalog. -> [!NOTE] -> Currently, OLM 1.0 does not support automatic upgrades to the next major version. You must manually verify and perform major version upgrades. For more information about major version upgrades, see [Manually verified upgrades and downgrades](#manually-verified-upgrades-and-downgrades). +!!! note + Currently, OLM 1.0 does not support automatic upgrades to the next major version. You must manually verify and perform major version upgrades. For more information about major version upgrades, see [Manually verified upgrades and downgrades](#manually-verified-upgrades-and-downgrades). ### Upgrades within the major version zero @@ -72,28 +82,28 @@ You must verify and perform upgrades manually in cases where automatic upgrades ## Manually verified upgrades and downgrades -**Warning:** If you want to force an upgrade manually, you must thoroughly verify the outcome before applying any changes to production workloads. Failure to test and verify the upgrade might lead to catastrophic consequences such as data loss. +!!! warning + If you want to force an upgrade manually, you must thoroughly verify the outcome before applying any changes to production workloads. Failure to test and verify the upgrade might lead to catastrophic consequences such as data loss. -As a package admin, if you must upgrade or downgrade to version that might be incompatible with the currently installed version, you can set the `.spec.upgradeConstraintPolicy` field to `SelfCertified` on the relevant `ClusterExtension` resource. +As a package admin, if you must upgrade or downgrade to version that might be incompatible with the currently installed version, you can set the `.spec.source.catalog.upgradeConstraintPolicy` field to `SelfCertified` on the relevant `ClusterExtension` resource. If you set the field to `SelfCertified`, no upgrade constraints are set on the package. As a result, you can change the version to any version available in the catalogs for a given package. -Example `ClusterExtension` with `.spec.upgradeConstraintPolicy` field set to `SelfCertified`: +Example `ClusterExtension` with `.spec.source.catalog.upgradeConstraintPolicy` field set to `SelfCertified`: ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: extension-sample spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator version: 0.6.0 upgradeConstraintPolicy: SelfCertified - install: - namespace: argocd - serviceAccout: - name: argocd-installer ``` diff --git a/docs/drafts/version-ranges.md b/docs/concepts/version-ranges.md similarity index 98% rename from docs/drafts/version-ranges.md rename to docs/concepts/version-ranges.md index d247cc19f5..97412deb21 100644 --- a/docs/drafts/version-ranges.md +++ b/docs/concepts/version-ranges.md @@ -4,7 +4,7 @@ This document explains how to specify a version range to install or update an ex You define a version range in a ClusterExtension's custom resource (CR) file. -## Specifying a version range in the CR +### Specifying a version range in the CR If you specify a version range in the ClusterExtension's CR, OLM 1.0 installs or updates the latest version of the extension that can be resolved within the version range. The resolved version is the latest version of the extension that satisfies the dependencies and constraints of the extension and the environment. @@ -31,7 +31,7 @@ You define a version range by adding a comparison string to the `spec.version` f To specify a version range, use a range comparison similar to the following example: ```yaml -version: >=3.0, <3.6 +version: ">=3.0, <3.6" ``` #### Wildcards in comparisons @@ -41,6 +41,7 @@ If you use a wildcard character with the `=` operator, you define a patch level This is equivalent to making a tilde range comparison. *Example comparisons with wildcard characters* + | Comparison | Equivalent | |------------|---------------------| | `1.2.x` | `>= 1.2.0, < 1.3.0` | @@ -55,6 +56,7 @@ You can use the tilde (`~`) operator to make patch release comparisons. This is useful when you want to specify a minor version up to the next major version. *Example patch release comparisons* + | Comparison | Equivalent | |------------|---------------------| | `~1.2.3` | `>= 1.2.3, < 1.3.0` | diff --git a/docs/contribute/contributing.md b/docs/contribute/contributing.md new file mode 120000 index 0000000000..f939e75f21 --- /dev/null +++ b/docs/contribute/contributing.md @@ -0,0 +1 @@ +../../CONTRIBUTING.md \ No newline at end of file diff --git a/docs/contribute/developer.md b/docs/contribute/developer.md new file mode 100644 index 0000000000..d33b98f200 --- /dev/null +++ b/docs/contribute/developer.md @@ -0,0 +1,173 @@ + +## Getting Started + +The following `make run` starts a [KIND](https://sigs.k8s.io/kind) cluster for you to get a local cluster for testing, see the manual install steps below for how to run against a remote cluster. + +!!! note + You will need a container runtime environment like Docker to run Kind. Kind also has experimental support for Podman. + + If you are on MacOS, see [Special Setup for MacOS](#special-setup-for-macos). + +### Quickstart Installation + +First, you need to install the CRDs and the operator-controller into a new [KIND cluster](https://kind.sigs.k8s.io/). You can do this by running: + +```sh +make run +``` + +This will build a local container image of the operator-controller, create a new KIND cluster and then deploy onto that cluster. This will also deploy the catalogd and cert-manager dependencies. + +### To Install Any Given Release + +!!! warning + Operator-Controller depends on [cert-manager](https://cert-manager.io/). Running the following command + may affect an existing installation of cert-manager and cause cluster instability. + +The latest version of Operator Controller can be installed with the following command: + +```bash +curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s +``` + +### Manual Step-by-Step Installation +1. Install Instances of Custom Resources: + + ```sh + kubectl apply -f config/samples/ + ``` + +2. Build and push your image to the location specified by `IMG`: + + ```sh + make docker-build docker-push IMG=/operator-controller:tag + ``` + +3. Deploy the controller to the cluster with the image specified by `IMG`: + + ```sh + make deploy IMG=/operator-controller:tag + ``` + +### Modifying the API definitions +If you are editing the API definitions, generate the manifests such as CRs or CRDs using: + +```sh +make manifests +``` + +!!! note + Run `make help` for more information on all potential `make` targets. + +### Rapid Iterative Development with Tilt + +If you are developing against the combined ecosystem of catalogd + operator-controller, you will want to take advantage of `tilt`: + +[Tilt](https://tilt.dev) is a tool that enables rapid iterative development of containerized workloads. + +Here is an example workflow without Tilt for modifying some source code and testing those changes in a cluster: + +1. Modify the source code. +2. Build the container image. +3. Either push the image to a registry or load it into your kind cluster. +4. Deploy all the appropriate Kubernetes manifests for your application. + +This process can take minutes, depending on how long each step takes. + +Here is the same workflow with Tilt: + +1. Run `tilt up` +2. Modify the source code +3. Wait for Tilt to update the container with your changes + +This ends up taking a fraction of the time, sometimes on the order of a few seconds! + +### Installing Tilt + +Follow Tilt's [instructions](https://docs.tilt.dev/install.html) for installation. + +### Installing catalogd + +operator-controller requires +[catalogd](https://github.com/operator-framework/operator-controller/tree/main/catalogd). When you give a `tilt up` invocation, catalogd will be started along with operator-controller. + +### Starting Tilt + +This is typically as short as: + +```shell +tilt up +``` + +!!! note + If you are using Podman, at least as of v4.5.1, you need to do this: + + ```shell + DOCKER_BUILDKIT=0 tilt up + ``` + + Otherwise, you'll see an error when Tilt tries to build your image that looks similar to: + + ```text + Build Failed: ImageBuild: stat /var/tmp/libpod_builder2384046170/build/Dockerfile: no such file or directory + ``` + +When Tilt starts, you'll see something like this in your terminal: + +```text +Tilt started on http://localhost:10350/ +v0.33.1, built 2023-06-28 + +(space) to open the browser +(s) to stream logs (--stream=true) +(t) to open legacy terminal mode (--legacy=true) +(ctrl-c) to exit +``` + +At the end of the installation process, the command output will prompt you to press the space bar to open the web UI, which provides a useful overview of all the installed components. + +Shortly after starting, Tilt processes the `Tiltfile`, resulting in: + +- Building the go binaries +- Building the images +- Loading the images into kind +- Running kustomize and applying everything except the Deployments that reference the images above +- Modifying the Deployments to use the just-built images +- Creating the Deployments + +--- + +## Special Setup for MacOS + +Some additional setup is necessary on Macintosh computers to install and configure compatible tooling. + +### Install Homebrew and tools +Follow the instructions to [install Homebrew](https://docs.brew.sh/Installation), and then execute the following command to install the required tools: + +```sh +brew install bash gnu-tar gsed coreutils +``` + +### Configure your shell +To configure your shell, either add this to your bash or zsh profile (e.g., in $HOME/.bashrc or $HOME/.zshrc), or run the following command in the terminal: + +```sh +for bindir in `find $(brew --prefix)/opt -type d -follow -name gnubin -print -maxdepth 3` +do + export PATH=$bindir:$PATH +done +``` + +--- + +## Making code changes + +Any time you change any of the files listed in the `deps` section in the `_binary` `local_resource`, +Tilt automatically rebuilds the go binary. As soon as the binary is rebuilt, Tilt pushes it (and only it) into the +appropriate running container, and then restarts the process. + +--- + +## Contributing + +Refer to [CONTRIBUTING.md](contributing.md) for more information. diff --git a/docs/css/extra.css b/docs/css/extra.css new file mode 100644 index 0000000000..0553b3b972 --- /dev/null +++ b/docs/css/extra.css @@ -0,0 +1,10 @@ +/* Hide banner title */ +.md-header__title { + visibility: hidden; +} + +/* Make top-level navigation items bold */ +.md-nav__item--active > .md-nav__link, /* Active top-level items */ +.md-nav__item--nested > .md-nav__link { /* Nested top-level items */ + font-weight: bold; +} \ No newline at end of file diff --git a/docs/draft/OWNERS b/docs/draft/OWNERS new file mode 100644 index 0000000000..c81ed41103 --- /dev/null +++ b/docs/draft/OWNERS @@ -0,0 +1,2 @@ +approvers: + - docs-draft-approvers diff --git a/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md b/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md new file mode 100644 index 0000000000..eb70149dac --- /dev/null +++ b/docs/draft/api-reference/catalogd-webserver-metas-endpoint.md @@ -0,0 +1,111 @@ +# Catalogd web server + +[Catalogd](https://github.com/operator-framework/operator-controller/tree/main/catalogd), the OLM v1 component for making catalog contents available on cluster, includes +a web server that serves catalog contents to clients via HTTP(S) endpoints. + +The endpoints to retrieve information about installable clusterextentions can be composed from the `.status.urls.base` of a `ClusterCatalog` resource with the selected access API path. + +Currently, there are two API endpoints: + +1. `api/v1/all` endpoint that provides access to the FBC metadata in entirety. + +As an example, to access the full FBC via the v1 API endpoint (indicated by path `api/v1/all`) where `.status.urls.base` is + +```yaml + urls: + base: https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio +``` + +the URL to access the service would be `https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio/api/v1/all` + +2. `api/v1/metas` endpoint that allows clients to retrieve filtered portions of the FBC. + +The metas endpoint accepts parameters which are one of the sub-types of the `Meta` [definition](https://github.com/operator-framework/operator-registry/blob/e15668c933c03e229b6c80025fdadb040ab834e0/alpha/declcfg/declcfg.go#L111-L114), following the pattern `/api/v1/metas?[&...]`. + +As an example, to access only the [package schema](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#olmpackage-1) blobs of the FBC via the `api/v1/metas` endpoint where `.status.urls.base` is + +```yaml + urls: + base: https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio +``` + +the URL to access the service would be `https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio/api/v1/metas?schema=olm.package` + +For more examples of valid queries that can be made to the `api/v1/metas` service endpoint, please see [Catalog Queries](../../howto/catalog-queries.md). + +!!! note + + The values of the `.status.urls` field in a `ClusterCatalog` resource are arbitrary string values and can change at any time. + While there are no guarantees on the exact value of this field, it will always contain catalog-specific API endpoints for use + by clients to make a request from within the cluster. + +## Interacting With the Server + +### Supported HTTP Methods + +The HTTP request methods supported by the catalogd web server are: + +- [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) +- [HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) + +### Response Format + +Responses are encoded as a [JSON Lines](https://jsonlines.org/) stream of [File-Based Catalog](https://olm.operatorframework.io/docs/reference/file-based-catalogs) (FBC) [Meta](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#schema) objects delimited by newlines. + +??? example "Example JSON-encoded FBC snippet" + + ```json + { + "schema": "olm.package", + "name": "cockroachdb", + "defaultChannel": "stable-v6.x", + } + { + "schema": "olm.channel", + "name": "stable-v6.x", + "package": "cockroachdb", + "entries": [ + { + "name": "cockroachdb.v6.0.0", + "skipRange": "<6.0.0" + } + ] + } + { + "schema": "olm.bundle", + "name": "cockroachdb.v6.0.0", + "package": "cockroachdb", + "image": "quay.io/openshift-community-operators/cockroachdb@sha256:d3016b1507515fc7712f9c47fd9082baf9ccb070aaab58ed0ef6e5abdedde8ba", + "properties": [ + { + "type": "olm.package", + "value": { + "packageName": "cockroachdb", + "version": "6.0.0" + } + }, + ], + } + ``` + + Corresponding JSON lines response: + ```jsonlines + {"schema":"olm.package","name":"cockroachdb","defaultChannel":"stable-v6.x"} + {"schema":"olm.channel","name":"stable-v6.x","package":"cockroachdb","entries":[{"name":"cockroachdb.v6.0.0","skipRange":"<6.0.0"}]} + {"schema":"olm.bundle","name":"cockroachdb.v6.0.0","package":"cockroachdb","image":"quay.io/openshift-community-operators/cockroachdb@sha256:d3016b1507515fc7712f9c47fd9082baf9ccb070aaab58ed0ef6e5abdedde8ba","properties":[{"type":"olm.package","value":{"packageName":"cockroachdb","version":"6.0.0"}}]} + ``` + +### Compression Support + +The `catalogd` web server supports gzip compression of responses, which can significantly reduce associated network traffic. In order to signal that the client handles compressed responses, the client must include `Accept-Encoding: gzip` as a header in the HTTP request. + +The web server will include a `Content-Encoding: gzip` header in compressed responses. + +!!! note + + Only catalogs whose uncompressed response body would result in a response size greater than 1400 bytes will be compressed. + +### Cache Header Support + +For clients interested in caching the information returned from the `catalogd` web server, the `Last-Modified` header is set +on responses and the `If-Modified-Since` header is supported for requests. diff --git a/docs/draft/api-reference/network-policies.md b/docs/draft/api-reference/network-policies.md new file mode 100644 index 0000000000..016825ebfd --- /dev/null +++ b/docs/draft/api-reference/network-policies.md @@ -0,0 +1,94 @@ +# NetworkPolicy in OLMv1 + +## Overview + +OLMv1 uses [Kubernetes NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) to secure communication between components, restricting network traffic to only what's necessary for proper functionality. + +* The catalogd NetworkPolicy is implemented [here](https://github.com/operator-framework/operator-controller/blob/main/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml). +* The operator-controller is implemented [here](https://github.com/operator-framework/operator-controller/blob/main/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml). + +This document explains the details of `NetworkPolicy` implementation for the core components. + + +## Implementation Overview + +NetworkPolicy is implemented for both catalogd and operator-controller components to: + +* Restrict incoming (ingress) traffic to only required ports and services +* Control outgoing (egress) traffic patterns + +Each component has a dedicated NetworkPolicy that applies to its respective pod through label selectors: + +* For catalogd: `app.kubernetes.io/name=catalogd` +* For operator-controller: `app.kubernetes.io/name=operator-controller` + +### Catalogd NetworkPolicy + +- Ingress Rules +Catalogd exposes three services, and its NetworkPolicy allows ingress traffic to the following TCP ports: + +* 7443: Metrics server for Prometheus metrics +* 8443: Catalogd HTTPS server for catalog metadata API +* 9443: Webhook server for Mutating Admission Webhook implementation + +All other ingress traffic to the catalogd pod is blocked. + +- Egress Rules +Catalogd needs to communicate with: + +* The Kubernetes API server +* Image registries specified in ClusterCatalog objects + +Currently, all egress traffic from catalogd is allowed, to support communication with arbitrary image registries that aren't known at install time. + +### Operator-Controller NetworkPolicy + +- Ingress Rules +Operator-controller exposes one service, and its NetworkPolicy allows ingress traffic to: + +* 8443: Metrics server for Prometheus metrics + +All other ingress traffic to the operator-controller pod is blocked. + +- Egress Rules +Operator-controller needs to communicate with: + +* The Kubernetes API server +* Catalogd's HTTPS server (on port 8443) +* Image registries specified in bundle metadata + +Currently, all egress traffic from operator-controller is allowed to support communication with arbitrary image registries that aren't known at install time. + +## Security Considerations + +The current implementation focuses on securing ingress traffic while allowing all egress traffic. This approach: + +* Prevents unauthorized incoming connections +* Allows communication with arbitrary image registries +* Establishes a foundation for future refinements to egress rules + +While allowing all egress does present some security risks, this implementation provides significant security improvements over having no network policies at all. + +## Troubleshooting Network Issues + +If you encounter network connectivity issues after deploying OLMv1, consider the following: + +* Verify NetworkPolicy support: Ensure your cluster has a CNI plugin that supports NetworkPolicy. If your Kubernetes cluster is using a Container Network Interface (CNI) plugin that doesn't support NetworkPolicy, then the NetworkPolicy resources you create will be completely ignored and have no effect whatsoever on traffic flow. +* Check pod labels: Confirm that catalogd and operator-controller pods have the correct labels for NetworkPolicy selection: + +```bash +# Verify catalogd pod labels +kubectl get pods -n olmv1-system --selector=apps.kubernetes.io/name=catalogd + +# Verify operator-controller pod labels +kubectl get pods -n olmv1-system --selector=apps.kubernetes.io/name=operator-controller + +# Compare with actual pod names +kubectl get pods -n olmv1-system | grep -E 'catalogd|operator-controller' +``` +* Inspect logs: Check component logs for connection errors + +For more comprehensive information on NetworkPolicy, see: + +- How NetworkPolicy is implemented with [network plugins](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) via the Container Network Interface (CNI) +- Installing [Network Policy Providers](https://kubernetes.io/docs/tasks/administer-cluster/network-policy-provider/) documentation. diff --git a/docs/draft/howto/catalog-queries-metas-endpoint.md b/docs/draft/howto/catalog-queries-metas-endpoint.md new file mode 100644 index 0000000000..25b45a7a41 --- /dev/null +++ b/docs/draft/howto/catalog-queries-metas-endpoint.md @@ -0,0 +1,93 @@ +# Catalog queries + +After you [add a catalog of extensions](../../tutorials/add-catalog.md) to your cluster, you must port forward your catalog as a service. +Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. + +## Prerequisites + +* You have added a ClusterCatalog of extensions, such as [OperatorHub.io](https://operatorhub.io), to your cluster. +* You have installed the `jq` CLI tool. + +!!! note + By default, Catalogd is installed with TLS enabled for the catalog webserver. + The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag. + +!!! note + While using the `/api/v1/metas` endpoint shown in the below examples, it is important to note that the metas endpoint accepts parameters which are one of the sub-types of the `Meta` [definition](https://github.com/operator-framework/operator-registry/blob/e15668c933c03e229b6c80025fdadb040ab834e0/alpha/declcfg/declcfg.go#L111-L114), following the pattern `/api/v1/metas?[&...]`. e.g. `schema=&package=`, `schema=&name=`, and `package=&name=` are all valid parameter combinations. However `schema=&version=` is not a valid parameter combination, since version is not a first class FBC meta field. + +You also need to port forward the catalog server service: + +``` terminal +kubectl -n olmv1-system port-forward svc/catalogd-service 8443:443 +``` + +Now you can use the `curl` command with `jq` to query catalogs that are installed on your cluster. + +## Package queries + +* Available packages in a catalog: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.package' + ``` + +* Packages that support `AllNamespaces` install mode and do not use webhooks: + ``` terminal + jq -cs '[.[] | select(.schema == "olm.bundle" and (.properties[] | select(.type == "olm.csv.metadata").value.installModes[] | select(.type == "AllNamespaces" and .supported == true)) and .spec.webhookdefinitions == null) | .package] | unique[]' + ``` + +* Package metadata: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.package&name=%3Cpackage_name%3E' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Blobs that belong to a package (that are not schema=olm.package): + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?package=%3Cpackage_name%3E' + ``` + + `` + : Name of the package from the catalog you are querying. + +Note: the `olm.package` schema blob does not have the `package` field set. In other words, to get all the blobs that belong to a package, along with the olm.package blob for that package, a combination of both of the above queries need to be used. + +## Channel queries + +* Channels in a package: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.channel&package=%3Cpackage_name%3E' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Versions in a channel: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.channel&package=zoperator&name=alpha' | jq -s '.[] | .entries | .[] | .name' + ``` + + `` + : Name of the package from the catalog you are querying. + + `` + : Name of the channel for a given package. + +## Bundle queries + +* Bundles in a package: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.bundle&package=%3Cpackage_name%3E' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Bundle dependencies and available APIs: + ``` terminal + curl -k 'https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.bundle&name=%3Cbundle_name%3E' | jq -s '.[] | .properties[] | select(.type=="olm.gvk")' + ``` + + `` + : Name of the bundle for a given package. diff --git a/docs/draft/howto/consuming-metrics.md b/docs/draft/howto/consuming-metrics.md new file mode 100644 index 0000000000..ccefbee6c2 --- /dev/null +++ b/docs/draft/howto/consuming-metrics.md @@ -0,0 +1,301 @@ +# Consuming Metrics + +!!! warning +Metrics endpoints and ports are available as an alpha release and are subject to change in future versions. +The following procedure is provided as an example for testing purposes. Do not depend on alpha features in production clusters. + +In OLM v1, you can use the provided metrics with tools such as the [Prometheus Operator][prometheus-operator]. By default, Operator Controller and catalogd export metrics to the `/metrics` endpoint of each service. + +You must grant the necessary permissions to access the metrics by using [role-based access control (RBAC) polices][rbac-k8s-docs]. You will also need to create a `NetworkPolicy` to allow egress traffic from your scraper pod, as the OLM namespace by default allows only `catalogd` and `operator-controller` to send and receive traffic. +Because the metrics are exposed over HTTPS by default, you need valid certificates to use the metrics with services such as Prometheus. +The following sections cover enabling metrics, validating access, and provide a reference of a `ServiceMonitor` +to illustrate how you might integrate the metrics with the [Prometheus Operator][prometheus-operator] or other third-part solutions. + +--- + +## Enabling metrics for the Operator Controller + +1. To enable access to the Operator controller metrics, create a `ClusterRoleBinding` resource by running the following command: + +```shell +kubectl create clusterrolebinding operator-controller-metrics-binding \ + --clusterrole=operator-controller-metrics-reader \ + --serviceaccount=olmv1-system:operator-controller-controller-manager +``` + +2. Next, create a `NetworkPolicy` to allow the scraper pods to send their scrape requests: + +```shell +kubectl apply -f - << EOF +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: scraper-policy + namespace: olmv1-system +spec: + podSelector: + matchLabels: + metrics: scraper + policyTypes: + - Egress + egress: + - {} # Allows all egress traffic for metrics requests +EOF +``` +### Validating Access Manually + +1. Generate a token for the service account and extract the required certificates: + +```shell +TOKEN=$(kubectl create token operator-controller-controller-manager -n olmv1-system) +echo $TOKEN +``` + +2. Apply the following YAML to deploy a pod in a namespace to consume the metrics: + +```shell +kubectl apply -f - <. + - ..svc + - ..svc.cluster.local + +#### Openshift-ServiceCA + +Generation and rotation are completely governed by [Openshift-ServiceCA](https://github.com/openshift/service-ca-operator) + +### How does it work? + +There's no change in the installation flow. Just install a bundle containing webhooks as you would any other. + +### Demo + +!!! note +As there is no difference in usage or experience between the CertManager and Openshift-ServiceCA variants, only +the cert-manager variant is demoed. + +[![asciicast](https://asciinema.org/a/GyjsB129GkUadeuxFhNuG4FcS.svg)](https://asciinema.org/a/GyjsB129GkUadeuxFhNuG4FcS) diff --git a/docs/draft/howto/profiling_with_pprof.md b/docs/draft/howto/profiling_with_pprof.md new file mode 100644 index 0000000000..01c0969d48 --- /dev/null +++ b/docs/draft/howto/profiling_with_pprof.md @@ -0,0 +1,297 @@ +# Profiling with Pprof + +!!! warning +Pprof bind port flag are available as an alpha release and are subject to change in future versions. + +[Pprof][pprof] is a useful tool for analyzing memory and CPU usage profiles. However, it is not +recommended to enable it by default in production environments. While it is great for troubleshooting, +keeping it enabled can introduce performance concerns and potential information leaks. + +Both components allow you to enable pprof by specifying the port it should bind to using the +`pprof-bind-address` flag. However, you must ensure that each component uses a unique port—using +the same port for multiple components is not allowed. Additionally, you need to export the corresponding +port in the service configuration for each component. + +The following steps are examples to demonstrate the required changes to enable Pprof for Operator-Controller and CatalogD. + +## Enabling Pprof for gathering the data + +### For Operator-Controller + +1. Run the following command to patch the Deployment and add the `--pprof-bind-address=:8082` flag: + +```shell +kubectl patch deployment $(kubectl get deployments -n olmv1-system -l apps.kubernetes.io/name=operator-controller -o jsonpath='{.items[0].metadata.name}') \ +-n olmv1-system --type='json' -p='[ + { + "op": "add", + "path": "/spec/template/spec/containers/0/args/-", + "value": "--pprof-bind-address=:8082" + } +]' +``` + +2. Once Pprof is enabled, you need to export port `8082` in the Service to make it accessible: + +```shell +kubectl patch service operator-controller-service -n olmv1-system --type='json' -p='[ + { + "op": "add", + "path": "/spec/ports/-", + "value": { + "name": "pprof", + "port": 8082, + "targetPort": 8082, + "protocol": "TCP" + } + } +]' +``` + +3. Create the Pod with `curl` to allow to generate the report: + +```shell +kubectl apply -f - < /tmp/operator-controller-profile.pprof" +``` + +6. Now, we can verify that the report was successfully created: + +```shell +kubectl exec -it curl-oper-con-pprof -n olmv1-system -- ls -lh /tmp/ +``` + +7. Then, we can copy the result for your local environment: + +```shell +kubectl cp olmv1-system/curl-oper-con-pprof:/tmp/operator-controller-profile.pprof ./operator-controller-profile.pprof +tar: removing leading '/' from member names +``` + +8. By last, we can use pprof to analyse the result: + +```shell +go tool pprof -http=:8080 ./operator-controller-profile.pprof +``` + +### For the CatalogD + +1. Run the following command to patch the Deployment and add the `--pprof-bind-address=:8083` flag: + +```shell +kubectl patch deployment $(kubectl get deployments -n olmv1-system -l apps.kubernetes.io/name=catalogd -o jsonpath='{.items[0].metadata.name}') \ +-n olmv1-system --type='json' -p='[ + { + "op": "add", + "path": "/spec/template/spec/containers/0/args/-", + "value": "--pprof-bind-address=:8083" + } +]' +``` + +2. Once Pprof is enabled, you need to export port `8083` in the `Service` to make it accessible: + +```shell +kubectl patch service $(kubectl get service -n olmv1-system -l app.kubernetes.io/part-of=olm,app.kubernetes.io/name=catalogd -o jsonpath='{.items[0].metadata.name}') \ +-n olmv1-system --type='json' -p='[ + { + "op": "add", + "path": "/spec/ports/-", + "value": { + "name": "pprof", + "port": 8083, + "targetPort": 8083, + "protocol": "TCP" + } + } +]' +``` + +3. Create the Pod with `curl` to allow to generate the report: + +```shell +kubectl apply -f - < /tmp/catalogd-profile.pprof" +``` + +6. Now, we can verify that the report was successfully created: + +```shell +kubectl exec -it curl-catalogd-pprof -n olmv1-system -- ls -lh /tmp/ +``` + +7. Then, we can copy the result for your local environment: + +```shell +kubectl cp olmv1-system/curl-catalogd-pprof:/tmp/catalogd-profile.pprof ./catalogd-profile.pprof +``` + +8. By last, we can use pprof to analyse the result: + +```shell +go tool pprof -http=:8080 ./catalogd-profile.pprof +``` + +## Disabling pprof after gathering the data + +### For Operator-Controller + +1. Run the following command to bind to `--pprof-bind-address` the value `0` in order to disable the endpoint. + +```shell +kubectl patch deployment $(kubectl get deployments -n olmv1-system -l apps.kubernetes.io/name=operator-controller -o jsonpath='{.items[0].metadata.name}') \ +-n olmv1-system --type='json' -p='[ + { + "op": "replace", + "path": "/spec/template/spec/containers/0/args", + "value": ["--pprof-bind-address=0"] + } +]' +``` + +2. Try to generate the report as done previously. The connection should now be refused: + +```shell +kubectl exec -it curl-pprof -n olmv1-system -- sh -c \ +"curl -s -k -H \"Authorization: Bearer $TOKEN\" \ +http://operator-controller-service.olmv1-system.svc.cluster.local:8082/debug/pprof/profile > /tmp/operator-controller-profile.pprof" +``` + +**NOTE:** if you wish you can delete the service port added to allow use pprof and +re-start the deployment `kubectl rollout restart deployment -n olmv1-system operator-controller-controller-manager` + +3. We can remove the Pod created to generate the report: + +```shell +kubectl delete pod curl-oper-con-pprof -n olmv1-system +``` + +### For CatalogD + +1. Run the following command to bind to `--pprof-bind-address` the value `0` in order to disable the endpoint. +```shell +kubectl patch deployment $(kubectl get deployments -n olmv1-system -l apps.kubernetes.io/name=catalogd -o jsonpath='{.items[0].metadata.name}') \ +-n olmv1-system --type='json' -p='[ + { + "op": "replace", + "path": "/spec/template/spec/containers/0/args", + "value": ["--pprof-bind-address=0"] + } +]' +``` + +2. To ensure we can try to generate the report as done above. Note that the connection +should be refused: + +```shell +kubectl exec -it curl-pprof -n olmv1-system -- sh -c \ +"curl -s -k -H \"Authorization: Bearer $TOKEN\" \ +http://catalogd-service.olmv1-system.svc.cluster.local:8083/debug/pprof/profile > /tmp/catalogd-profile.pprof" +``` + +**NOTE:** if you wish you can delete the service port added to allow use pprof and +re-start the deployment `kubectl rollout restart deployment -n olmv1-system catalogd-controller-manager` + +3. We can remove the Pod created to generate the report: + +```shell +kubectl delete pod curl-catalogd-pprof -n olmv1-system +``` + +[pprof]: https://github.com/google/pprof/blob/main/doc/README.md diff --git a/docs/draft/howto/rbac-permissions-checking.md b/docs/draft/howto/rbac-permissions-checking.md new file mode 100644 index 0000000000..04c13bf0ce --- /dev/null +++ b/docs/draft/howto/rbac-permissions-checking.md @@ -0,0 +1,54 @@ +# How To Get Your Cluster Extension RBAC Right — Working with the Preflight Permissions Check + +Cluster Extensions in Operator Lifecycle Manager (OLM) v1 are installed and managed via a **service account** that you (the cluster admin) provide. Unlike OLM v0, OLM v1 itself doesn’t have cluster-admin privileges to grant Operators the access they need – **you** must ensure the service account has all necessary Role-Based Access Control (RBAC) permissions. If the service account is missing permissions, the extension’s installation will fail or hang. To address this, the **operator-controller** now performs a **preflight permissions check** before installing an extension. This check identifies any missing RBAC permissions up front and surfaces them to you so that you can fix the issues. + +## Understanding the Preflight Permissions Check + +When you create a `ClusterExtension` Custom Resource (CR) to install an Operator extension, the operator-controller will do a dry-run of the installation and verify that the specified service account can perform all the actions required by that extension. This includes creating all the Kubernetes objects in the bundle (Deployments, Services, CRDs, etc.), as well as creating any RBAC roles or bindings that the extension’s bundle defines. + +If any required permission is missing, the preflight check will **fail fast** *before* attempting the real installation. Instead of proceeding, the operator-controller records which permissions are missing. You’ll find this information in two places: + +- **ClusterExtension Status Conditions:** The `ClusterExtension` CR will have a condition (such as **Progressing** or **Installing**) with a message describing the missing permissions. The condition’s reason may be set to “Retrying” (meaning the controller will periodically retry the install) and the message will start with “pre-authorization failed: …”. +- **Operator-Controller Logs:** The same message is also logged by the operator-controller pod. If you have access to the operator-controller’s logs (in namespace `olm-controller` on OpenShift), you can see the detailed RBAC errors there as well. + +### Interpreting the Preflight Check Output + +The preflight check’s output enumerates the **RBAC rules** that the service account is missing. Each missing permission is listed in a structured format. For example, a message might say: + +``` +service account requires the following permissions to manage cluster extension: + Namespace:"" APIGroups:[] Resources:[services] Verbs:[list,watch] + Namespace:"pipelines" APIGroups:[] Resources:[secrets] Verbs:[get] +``` + +Let’s break down how to read this output: + +- **`Namespace:""`** – An empty namespace in quotes means the permission is needed at the **cluster scope** (not limited to a single namespace). In the example above, `Namespace:""` for Services indicates the service account needs the ability to list/watch Services cluster-wide. +- **`APIGroups:[]`** – An empty API group (`[]`) means the **core API group** (no group). For instance, core resources like Services, Secrets, ConfigMaps have `APIGroups:[]`. If the resource is part of a named API group (e.g. `apps`, `apiextensions.k8s.io`), that group would be listed here. +- **`Resources:[...]`** – The resource type that’s missing permissions. e.g. `services`, `secrets`, `customresourcedefinitions`. +- **`Verbs:[...]`** – The specific actions (verbs) that the service account is not allowed to do for that resource. Multiple verbs listed together means none of those verbs are permitted (and are all required). + +A few special cases to note: + +- **Privilege Escalation Cases:** If the extension’s bundle includes the creation of a Role or ClusterRole, the service account needs to have at least the permissions it is trying to grant. If not, the preflight check will report those verbs as missing to prevent privilege escalation. +- **Missing Role References (Resolution Errors):** If an Operator’s bundle references an existing ClusterRole or Role that is not found, the preflight check will report an “authorization evaluation error” listing the missing role. + +## Resolving Common RBAC Permission Errors + +Once you understand what each missing permission is, the fix is usually straightforward: **grant the service account those permissions**. Here are common scenarios and how to address them: + +- **Missing resource permissions (verbs):** Update or create a (Cluster)Role and RoleBinding/ClusterRoleBinding to grant the missing verbs on the resources in the specified namespaces or at cluster scope. +- **Privilege escalation missing permissions:** Treat these missing verbs as required for the installer as well, granting the service account those rights so it can create the roles it needs. +- **Missing roles/clusterroles:** Ensure any referenced roles exist by creating them or adjusting the extension’s expectations. + +## Demo Scenario (OpenShift) + +Below is an example demo you can run on OpenShift to see the preflight check in action: + +1. **Create a minimal ServiceAccount and ClusterRole** that lacks key permissions (e.g., missing list/watch on Services and create on CRDs). +2. **Apply a ClusterExtension** pointing to the Pipelines Operator package, specifying the above service account. +3. **Describe the ClusterExtension** (`oc describe clusterextension pipelines-operator`) to see the preflight “pre-authorization failed” errors listing missing permissions. +4. **Update the ClusterRole** to include the missing verbs. +5. **Reapply the role** and observe the ClusterExtension status clear and the operator proceed with installation. + +By following this guide and using the preflight output, you can iteratively grant exactly the permissions needed—no more, no less—ensuring your cluster extensions install reliably and securely. diff --git a/docs/draft/howto/single-ownnamespace-install.md b/docs/draft/howto/single-ownnamespace-install.md new file mode 100644 index 0000000000..8e6f00fe49 --- /dev/null +++ b/docs/draft/howto/single-ownnamespace-install.md @@ -0,0 +1,139 @@ +## Description + +!!! note +This feature is still in *alpha* the `SingleOwnNamespaceInstallSupport` feature-gate must be enabled to make use of it. +See the instructions below on how to enable it. + +--- + +A component of OLMv0's multi-tenancy feature is its support of four [*installModes*](https://olm.operatorframework.io/docs/advanced-tasks/operator-scoping-with-operatorgroups/#targetnamespaces-and-their-relationship-to-installmodes): +for operator installation: + + - *OwnNamespace*: If supported, the operator can be configured to watch for events in the namespace it is deployed in. + - *SingleNamespace*: If supported, the operator can be configured to watch for events in a single namespace that the operator is not deployed in. + - *MultiNamespace*: If supported, the operator can be configured to watch for events in more than one namespace. + - *AllNamespaces*: If supported, the operator can be configured to watch for events in all namespaces. + +OLMv1 will not attempt multi-tenancy (see [design decisions document](../../project/olmv1_design_decisions.md)) and will think of operators +as globally installed, i.e. in OLMv0 parlance, as installed in *AllNamespaces* mode. However, there are operators that +were intended only for the *SingleNamespace* and *OwnNamespace* install modes. In order to make these operators installable in v1 while they +transition to the new model, v1 is adding support for these two new *installModes*. It should be noted that, in line with v1's no multi-tenancy policy, +users will not be able to install the same operator multiple times, and that in future iterations of the registry bundle format will not +include *installModes*. + +## Demos + +### SingleNamespace Install + +[![SingleNamespace Install Demo](https://asciinema.org/a/w1IW0xWi1S9cKQFb9jnR07mgh.svg)](https://asciinema.org/a/w1IW0xWi1S9cKQFb9jnR07mgh) + +### OwnNamespace Install + +[![OwnNamespace Install Demo](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i.svg)](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i) + +## Enabling the Feature-Gate + +!!! tip + +This guide assumes OLMv1 is already installed. If that is not the case, +you can follow the [getting started](../../getting-started/olmv1_getting_started.md) guide to install OLMv1. + +--- + +Patch the `operator-controller` `Deployment` adding `--feature-gates=SingleOwnNamespaceInstallSupport=true` to the +controller container arguments: + +```terminal title="Enable SingleOwnNamespaceInstallSupport feature-gate" +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' +``` + +Wait for `Deployment` rollout: + +```terminal title="Wait for Deployment rollout" +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager +``` + +## Configuring the `ClusterExtension` + +A `ClusterExtension` can be configured to install bundle in `Single-` or `OwnNamespace` mode through the +`.spec.config.inline.watchNamespace` property which may or may not be present or required depending on the bundle's +install mode support, if the bundle: + + - only supports *AllNamespaces* mode => `watchNamespace` is not a configuration + - supports *AllNamespaces* and *SingleNamespace* and/or *OwnNamespace* => `watchNamespace` is optional + - bundle only supports *SingleNamespace* and/or *OwnNamespace* => `watchNamespace` is required + +The `watchNamespace` configuration can only be the install namespace if the bundle supports the *OwnNamespace* install mode, and +it can only be any other namespace if the bundle supports the *SingleNamespace* install mode. + +Examples: + +Bundle only supports *AllNamespaces*: +- `watchNamespace` is not a configuration +- bundle will be installed in *AllNamespaces* mode + +Bundle only supports *OwnNamespace*: +- `watchNamespace` is required +- `watchNamespace` must be the install namespace +- bundle will always be installed in *OwnNamespace* mode + +Bundle supports *AllNamespace* and *OwnNamespace*: +- `watchNamespace` is optional +- if `watchNamespace` = install namespace => bundle will be installed in *OwnNamespace* mode +- if `watchNamespace` is null or not set => bundle will be installed in *AllNamespaces* mode +- if `watchNamespace` != install namespace => error + +Bundle only supports *SingleNamespace*: +- `watchNamespace` is required +- `watchNamespace` must *NOT* be the install namespace +- bundle will always be installed in *SingleNamespace* mode + +Bundle supports *AllNamespaces*, *SingleNamespace*, and *OwnNamespace* install modes: +- `watchNamespace` can be optionally configured +- if `watchNamespace` = install namespace => bundle will be installed in *OwnNamespace* mode +- if `watchNamespace` != install namespace => bundle will be installed in *SingleNamespace* mode +- if `watchNamespace` is null or not set => bundle will be installed in *AllNamespaces* mode + +### Examples + +``` terminal title="SingleNamespace install mode example" +kubectl apply -f - <" +and group "olm:clusterextensions" limiting Kubernetes API access scope to those defined for this user and group. These +users and group do not exist beyond being defined in Cluster/RoleBinding(s) and can only be impersonated by clients with + `impersonate` verb permissions on the `users` and `groups` resources. + +### Demo + +[![asciicast](https://asciinema.org/a/Jbtt8nkV8Dm7vriHxq7sxiVvi.svg)](https://asciinema.org/a/Jbtt8nkV8Dm7vriHxq7sxiVvi) + +#### Examples: + +##### ClusterExtension management as cluster-admin + +To enable ClusterExtensions management as cluster-admin, bind the `cluster-admin` cluster role to the `olm:clusterextensions` +group: + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clusterextensions-group-admin-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: Group + name: "olm:clusterextensions" +``` + +##### Scoped olm:clusterextension group + Added perms on specific extensions + +Give ClusterExtension management group broad permissions to manage ClusterExtensions denying potentially dangerous +permissions such as being able to read cluster wide secrets: + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: clusterextension-installer +rules: + - apiGroups: [ olm.operatorframework.io ] + resources: [ clusterextensions/finalizers ] + verbs: [ update ] + - apiGroups: [ apiextensions.k8s.io ] + resources: [ customresourcedefinitions ] + verbs: [ create, list, watch, get, update, patch, delete ] + - apiGroups: [ rbac.authorization.k8s.io ] + resources: [ clusterroles, roles, clusterrolebindings, rolebindings ] + verbs: [ create, list, watch, get, update, patch, delete ] + - apiGroups: [""] + resources: [configmaps, endpoints, events, pods, pod/logs, serviceaccounts, services, services/finalizers, namespaces, persistentvolumeclaims] + verbs: ['*'] + - apiGroups: [apps] + resources: [ '*' ] + verbs: ['*'] + - apiGroups: [ batch ] + resources: [ '*' ] + verbs: [ '*' ] + - apiGroups: [ networking.k8s.io ] + resources: [ '*' ] + verbs: [ '*' ] + - apiGroups: [authentication.k8s.io] + resources: [tokenreviews, subjectaccessreviews] + verbs: [create] +``` + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clusterextension-installer-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: clusterextension-installer +subjects: +- kind: Group + name: "olm:clusterextensions" +``` + +Give a specific ClusterExtension secrets access, maybe even on specific namespaces: + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: clusterextension-privileged +rules: +- apiGroups: [""] + resources: [secrets] + verbs: ['*'] +``` + +``` +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: clusterextension-privileged-binding + namespace: +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: clusterextension-privileged +subjects: +- kind: User + name: "olm:clusterextensions:argocd-operator" +``` + +Note: In this example the ClusterExtension user (or group) will still need to be updated to be able to manage +the CRs coming from the argocd operator. Some look ahead and RBAC permission wrangling will still be required. diff --git a/docs/draft/project/personas.md b/docs/draft/project/personas.md new file mode 100644 index 0000000000..16bf9dd6f5 --- /dev/null +++ b/docs/draft/project/personas.md @@ -0,0 +1,103 @@ +# OLM Personas + +This document attempts to identify essential roles in the OLM lifecycle and associate the duties logically performed by each role. Though some roles can be (and even may typically be) performed by the same actor, they are logically distinct roles with different goals. + +OLM roles are broadly categorized here as **Producers** or **Consumers**, indicating whether that role typically is producing content for use in the ecosystem or is using (consuming) content. + +# Consumers +## Cluster Admin +*Who is it?* + +This role encompasses the basic full-permissions-required creation/maintenance of a cluster, and any non-OLM-ecosystem activities, such as creating, scaling, and upgrading a cluster. + +*What does it do?* + +- Creates cluster +- Scales cluster +- Miscellaneous Cluster Administration +- Upgrades cluster + +## Cluster Extension Admin +*Who is it?* + +This role encompasses privileged operations required for OLMv1 and associated operators to deploy workloads to the cluster. This role may exist as a set of activities executed by a cluster admin, but also may operate independently of that role, depending on the necessary privileges. +Here `extension` represents any individual OLMv1 installable, including (but not limited to) `registry+v1` bundles. + +*What does it do?* + +- Creates enabling infrastructure for extension lifecycle (service accounts, etc.) +- Installs extensions +- Upgrades extensions +- Removes extensions +- Browses extensions offered in installed `ClusterCatalogs` +- Derives minimum privilege for installation +- filters visibility on installable extensions +- Verifies that extension health is detectable to desired sensors + +## Cluster Catalog Admin +*Who is it?* + +This role encompasses the control of `ClusterCatalogs` on the running cluster. This role may exist as a set of activities executed by a cluster admin, but also may operate independently of that role, depending on the necessary privileges. This role is a collaboration with **Catalog Curators** and may also interact with **Catalog Manipulators** + +*What does it do?* + +- Adds/removes/updates catalogs +- Enables/disables catalogs +- Configures pull secrets necessary to access extensions from catalogs + +## Cluster Monitors +*Who is it?* + +This role represents any actor which monitors the status of the cluster and installed workloads. This may include +- Platform status +- Extension health +- Diagnostic notifications + + +# Producers +## Extension Author +*Who is it?* + +This role encompasses folks who want to create an extension. It interacts with other **Producer** roles by generating a _catalog contribution_ to make extensions available on-cluster to **Cluster Extension Admins**. For example, a catalog contribution for a registry+v1 bundle is one/more bundle image and the upgrade graph expressed in [FBC](https://olm.operatorframework.io/docs/reference/file-based-catalogs/). + +*What does it do?* +- Creates extension +- Builds/releases extension +- Validates extension +- Adjusts upgrade graph +- Publishes artifacts (i.e. images for registry+v1 bundle) + +## Contribution Curator +*Who is it?* + +This role is responsible for taking catalog contributions from **Extension Authors**, applying any changes necessary for publication, and supplying the resulting artifacts to the **Catalog Curator**. This role is frequently fulfilled by different developers than **Extension Authors**. + +*What does it do?* +- Validates contributions +- Publishes contributions to registry + +## Catalog Curator +*Who is it?* + +This role is responsible for publishing a catalog index image to be used by **Consumers** to make workloads available on-cluster. Typically this role operates over multiple extensions, versions, and versioned releases of the final, published catalog. + +*What does it do?* +- Aggregates contributions +- Validates aggregate catalog +- Publishes aggregate catalog + +## Catalog Manipulator +*Who is it?* + +This role is a general category for users who consume published catalogs and re-publish them in some way. Possible use-cases include +- Restricting available extension versions +- Providing enclave services to disconnected environments +- Reducing catalog size by restricting the number of included extensions + +*What does it do?* +- Filters content +- Defines content access mapping to new environments (if modified) +- Provides catalog access in restricted environments + + + diff --git a/docs/draft/tutorials/explore-available-content-metas-endpoint.md b/docs/draft/tutorials/explore-available-content-metas-endpoint.md new file mode 100644 index 0000000000..f17271d3e4 --- /dev/null +++ b/docs/draft/tutorials/explore-available-content-metas-endpoint.md @@ -0,0 +1,147 @@ +--- +hide: + - toc +--- + +# Explore Available Content + +After you [add a catalog of extensions](../../tutorials/add-catalog.md) to your cluster, you must port forward your catalog as a service. +Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. + +## Prerequisites + +* You have added a ClusterCatalog of extensions, such as [OperatorHub.io](https://operatorhub.io), to your cluster. +* You have installed the `jq` CLI tool. + +!!! note + By default, Catalogd is installed with TLS enabled for the catalog webserver. + The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag. + +## Procedure + +1. Port forward the catalog server service: + + ``` terminal + kubectl -n olmv1-system port-forward svc/catalogd-service 8443:443 + ``` + +2. Return a list of all the extensions in a catalog via the v1 API endpoint: + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.package' | jq -s '.[] | .name' + ``` + + ??? success + ``` text title="Example output" + "ack-acm-controller" + "ack-acmpca-controller" + "ack-apigatewayv2-controller" + "ack-applicationautoscaling-controller" + "ack-cloudfront-controller" + "ack-cloudtrail-controller" + "ack-cloudwatch-controller" + "ack-cloudwatchlogs-controller" + "ack-dynamodb-controller" + "ack-ec2-controller" + "ack-ecr-controller" + "ack-ecs-controller" + "ack-efs-controller" + "ack-eks-controller" + "ack-elasticache-controller" + "ack-emrcontainers-controller" + "ack-eventbridge-controller" + "ack-iam-controller" + "ack-kafka-controller" + "ack-keyspaces-controller" + "ack-kinesis-controller" + "ack-kms-controller" + "ack-lambda-controller" + "ack-memorydb-controller" + "ack-mq-controller" + "ack-networkfirewall-controller" + "ack-opensearchservice-controller" + "ack-pipes-controller" + "ack-prometheusservice-controller" + "ack-rds-controller" + "ack-route53-controller" + "ack-route53resolver-controller" + "ack-s3-controller" + "ack-sagemaker-controller" + "ack-secretsmanager-controller" + "ack-sfn-controller" + "ack-sns-controller" + "ack-sqs-controller" + "aerospike-kubernetes-operator" + "airflow-helm-operator" + "aiven-operator" + "akka-cluster-operator" + "alvearie-imaging-ingestion" + "anchore-engine" + "apch-operator" + "api-operator" + "api-testing-operator" + "apicast-community-operator" + "apicurio-registry" + "apimatic-kubernetes-operator" + "app-director-operator" + "appdynamics-operator" + "application-services-metering-operator" + "appranix" + "aqua" + "argocd-operator" + ... + ``` + + !!! important + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. + +3. Return list of packages which support `AllNamespaces` install mode, do not use webhooks, and where the channel head version uses `olm.csv.metadata` format: + + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.bundle | jq -cs '[.[] | select(.properties[] | select(.type == "olm.csv.metadata").value.installModes[] | select(.type == "AllNamespaces" and .supported == true) and .spec.webhookdefinitions == null) | .package] | unique[]' + ``` + + ??? success + ``` text title="Example output" + "ack-acm-controller" + "ack-acmpca-controller" + "ack-apigateway-controller" + "ack-apigatewayv2-controller" + "ack-applicationautoscaling-controller" + "ack-athena-controller" + "ack-cloudfront-controller" + "ack-cloudtrail-controller" + "ack-cloudwatch-controller" + "ack-cloudwatchlogs-controller" + "ack-documentdb-controller" + "ack-dynamodb-controller" + "ack-ec2-controller" + "ack-ecr-controller" + "ack-ecs-controller" + ... + ``` + +4. Inspect the contents of an extension's metadata: + + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/metas?schema=olm.package&name= + ``` + + `package_name` + : Specifies the name of the package you want to inspect. + + ??? success + ``` text title="Example output" + { + "defaultChannel": "stable-v6.x", + "icon": { + "base64data": "PHN2ZyB4bWxucz0ia... + "mediatype": "image/svg+xml" + }, + "name": "cockroachdb", + "schema": "olm.package" + } + ``` + +### Additional resources + +* [Catalog queries](../../howto/catalog-queries.md) diff --git a/docs/drafts/Tasks/create-installer-service-account.md b/docs/drafts/Tasks/create-installer-service-account.md deleted file mode 100644 index e66c06076e..0000000000 --- a/docs/drafts/Tasks/create-installer-service-account.md +++ /dev/null @@ -1,3 +0,0 @@ -# Create Installer Service Account - -Placeholder. We need to document this. \ No newline at end of file diff --git a/docs/drafts/Tasks/downgrading-an-extension.md b/docs/drafts/Tasks/downgrading-an-extension.md deleted file mode 100644 index 58d9ec846e..0000000000 --- a/docs/drafts/Tasks/downgrading-an-extension.md +++ /dev/null @@ -1,3 +0,0 @@ -# Downgrade an Extension - -Placeholder. This topic is under development. \ No newline at end of file diff --git a/docs/drafts/Tasks/upgrading-an-extension.md b/docs/drafts/Tasks/upgrading-an-extension.md deleted file mode 100644 index 648093e87a..0000000000 --- a/docs/drafts/Tasks/upgrading-an-extension.md +++ /dev/null @@ -1,169 +0,0 @@ -# Upgrading an Extension - -Existing extensions can be upgraded by updating the version field in the ClusterExtension resource. - -For information on downgrading an extension, see [Downgrade an Extension](downgrading-an-extension). - -## Prerequisites - -* You have an extension installed -* The target version is compatible with OLM v1 (see [OLM v1 limitations](../refs/olmv1-limitations.md)) -* CRD compatibility between the versions being upgraded or downgraded (see [CRD upgrade safety](../../refs/crd-upgrade-safety.md)) -* The installer service account's RBAC permissions are adequate for the target version (see [Minimal RBAC for Installer Service Account](create-installer-service-account.md)) - -For more detailed information see [Upgrade Support](../upgrade-support.md). - -## Procedure - -Suppose we have successfully created and installed v0.5.0 of the ArgoCD operator with the following `ClusterExtension`: - -``` yaml title="Example CR" -apiVersion: olm.operatorframework.io/v1alpha1 -kind: ClusterExtension -metadata: - name: argocd -spec: - source: - sourceType: Catalog - catalog: - packageName: argocd-operator - version: 0.5.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer -``` - -* Update the version field in the ClusterExtension resource: - - ``` terminal - kubectl apply -f - < --type='merge' -p '{"spec": {"source": {"catalog": {"version": ""}}}}' - ``` - - `extension_name` - : Specifies the name defined in the `metadata.name` field of the extension's CR. - - `target_version` - : Specifies the version to upgrade or downgrade to. - - ??? success - ``` text title="Example output" - clusterextension.olm.operatorframework.io/argocd-operator patched - ``` - -### Verification - -* Verify that the Kubernetes extension is deleted: - - ``` terminal - kubectl get clusterextension.olm.operatorframework.io/ - ``` - - ??? success - ``` text title="Example output" - Name: argocd - Namespace: - Labels: olm.operatorframework.io/owner-kind=ClusterExtension - olm.operatorframework.io/owner-name=argocd - Annotations: - API Version: olm.operatorframework.io/v1alpha1 - Kind: ClusterExtension - Metadata: - Creation Timestamp: 2024-09-06T13:38:38Z - Finalizers: - olm.operatorframework.io/cleanup-unpack-cache - olm.operatorframework.io/cleanup-contentmanager-cache - Generation: 5 - Resource Version: 21167 - UID: 5abdf57d-aedc-45d4-ba0d-a86e785fd34a - Spec: - Install: - Namespace: argocd - Service Account: - Name: argocd-installer - Source: - Catalog: - Package Name: argocd-operator - Selector: - Upgrade Constraint Policy: Enforce - Version: 0.6.0 - Source Type: Catalog - Status: - Conditions: - Last Transition Time: 2024-09-06T13:38:38Z - Message: - Observed Generation: 5 - Reason: Deprecated - Status: False - Type: Deprecated - Last Transition Time: 2024-09-06T13:38:38Z - Message: - Observed Generation: 5 - Reason: Deprecated - Status: False - Type: PackageDeprecated - Last Transition Time: 2024-09-06T13:38:38Z - Message: - Observed Generation: 5 - Reason: Deprecated - Status: False - Type: ChannelDeprecated - Last Transition Time: 2024-09-06T13:38:38Z - Message: - Observed Generation: 5 - Reason: Deprecated - Status: False - Type: BundleDeprecated - Last Transition Time: 2024-09-06T13:40:14Z - Message: resolved to "quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3" - Observed Generation: 5 - Reason: Success - Status: True - Type: Resolved - Last Transition Time: 2024-09-06T13:38:38Z - Message: unpack successful: - Observed Generation: 5 - Reason: UnpackSuccess - Status: True - Type: Unpacked - Last Transition Time: 2024-09-06T13:40:31Z - Message: Installed bundle quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3 successfully - Observed Generation: 5 - Reason: Success - Status: True - Type: Installed - Install: - Bundle: - Name: argocd-operator.v0.6.0 - Version: 0.6.0 - Resolution: - Bundle: - Name: argocd-operator.v0.6.0 - Version: 0.6.0 - ``` diff --git a/docs/drafts/developer.md b/docs/drafts/developer.md deleted file mode 100644 index dbaf3999b6..0000000000 --- a/docs/drafts/developer.md +++ /dev/null @@ -1,195 +0,0 @@ -## Getting Started -You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. - -> [!NOTE] -> Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows). - -### Installation - -> [!CAUTION] -> Operator-Controller depends on [cert-manager](https://cert-manager.io/). Running the following command -> may affect an existing installation of cert-manager and cause cluster instability. - -The latest version of Operator Controller can be installed with the following command: - -```bash -curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s -``` - -### Additional setup on Macintosh computers -On Macintosh computers some additional setup is necessary to install and configure compatible tooling. - -#### Install Homebrew and tools -Follow the instructions to [installing Homebrew](https://docs.brew.sh/Installation) and then execute the following to install tools: - -```sh -brew install bash gnu-tar gsed -``` - -#### Configure your shell -Modify your login shell's `PATH` to prefer the new tools over those in the existing environment. This example should work either with `zsh` (in $HOME/.zshrc) or `bash` (in $HOME/.bashrc): - -```sh -for bindir in `find $(brew --prefix)/opt -type d -follow -name gnubin -print` -do - export PATH=$bindir:$PATH -done -``` - -### Running on the cluster -1. Install Instances of Custom Resources: - -```sh -kubectl apply -f config/samples/ -``` - -2. Build and push your image to the location specified by `IMG`: - -```sh -make docker-build docker-push IMG=/operator-controller:tag -``` - -3. Deploy the controller to the cluster with the image specified by `IMG`: - -```sh -make deploy IMG=/operator-controller:tag -``` - -### Uninstall CRDs -To delete the CRDs from the cluster: - -```sh -make uninstall -``` - -### Undeploy controller -To undeploy the controller from the cluster: - -```sh -make undeploy -``` - -### How it works -This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/). - -It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) -which provide a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster. - -### Test It Out - -Install the CRDs and the operator-controller into a new [KIND cluster](https://kind.sigs.k8s.io/): -```sh -make run -``` -This will build a local container image of the operator-controller, create a new KIND cluster and then deploy onto that cluster. -This will also deploy the catalogd and cert-manager dependencies. - -### Modifying the API definitions -If you are editing the API definitions, generate the manifests such as CRs or CRDs using: - -```sh -make manifests -``` - -**NOTE:** Run `make help` for more information on all potential `make` targets. - -### Rapid iterative development with Tilt - -If you are developing against the combined ecosystem of catalogd + operator-controller you will want to take advantage of `tilt`: - -[Tilt](https://tilt.dev) is a tool that enables rapid iterative development of containerized workloads. - -Here is an example workflow without Tilt for modifying some source code and testing those changes in a cluster: - -1. Modify the source code. -2. Build the container image. -3. Either push the image to a registry or load it into your kind cluster. -4. Deploy all the appropriate Kubernetes manifests for your application. - 1. Or, if this is an update, you'd instead scale the Deployment to 0 replicas, scale back to 1, and wait for the - new pod to be running. - -This process can take minutes, depending on how long each step takes. - -Here is the same workflow with Tilt: - -1. Run `tilt up` -2. Modify the source code -3. Wait for Tilt to update the container with your changes - -This ends up taking a fraction of the time, sometimes on the order of a few seconds! - -### Installing Tilt - -Follow Tilt's [instructions](https://docs.tilt.dev/install.html) for installation. - -### Installing catalogd - -operator-controller requires -[catalogd](https://github.com/operator-framework/catalogd). Please make sure it's installed, either normally or via -their own Tiltfiles, before proceeding. If you want to use Tilt, make sure you specify a unique `--port` flag to each -`tilt up` invocation. - -### Install tilt-support Repo - -You must install the tilt-support repo at the directory level above this repo: - -```bash -pushd .. -git clone https://github.com/operator-framework/tilt-support -popd -``` - -### Starting Tilt - -This is typically as short as: - -```shell -tilt up -``` - -**NOTE:** if you are using Podman, at least as of v4.5.1, you need to do this: - -```shell -DOCKER_BUILDKIT=0 tilt up -``` - -Otherwise, you'll see an error when Tilt tries to build your image that looks similar to: - -```text -Build Failed: ImageBuild: stat /var/tmp/libpod_builder2384046170/build/Dockerfile: no such file or directory -``` - -When Tilt starts, you'll see something like this in your terminal: - -```text -Tilt started on http://localhost:10350/ -v0.33.1, built 2023-06-28 - -(space) to open the browser -(s) to stream logs (--stream=true) -(t) to open legacy terminal mode (--legacy=true) -(ctrl-c) to exit -``` - -Typically, you'll want to press the space bar to have it open the UI in your web browser. - -Shortly after starting, Tilt processes the `Tiltfile`, resulting in: - -- Building the go binaries -- Building the images -- Loading the images into kind -- Running kustomize and applying everything except the Deployments that reference the images above -- Modifying the Deployments to use the just-built images -- Creating the Deployments - -### Making code changes - -Any time you change any of the files listed in the `deps` section in the `_binary` `local_resource`, -Tilt automatically rebuilds the go binary. As soon as the binary is rebuilt, Tilt pushes it (and only it) into the -appropriate running container, and then restarts the process. - ---- - -## Contributing - -Refer to [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. \ No newline at end of file diff --git a/docs/drafts/permissions-for-owner-references-permission-enforcement-plugin.md b/docs/drafts/permissions-for-owner-references-permission-enforcement-plugin.md deleted file mode 100644 index f80d332e06..0000000000 --- a/docs/drafts/permissions-for-owner-references-permission-enforcement-plugin.md +++ /dev/null @@ -1,13 +0,0 @@ -# Configuring a service account when the cluster uses the `OwnerReferencesPermissionEnforcement` admission plugin - -The [`OwnerReferencesPermissionEnforcement`](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement) admission plugin requires a user to have permission to set finalizers on owner objects when creating or updating an object to contain an `ownerReference` with `blockOwnerDeletion: true`. - -When operator-controller installs or upgrades a `ClusterExtension`, it sets an `ownerReference` on each object with `blockOwnerDeletion: true`. Therefore serviceaccounts configured in `.spec.serviceAccount.name` must have the following permission in a bound `ClusterRole`: - - ```yaml - - apiGroups: ["olm.operatorframework.io"] - resources: ["clusterextensions/finalizers"] - verbs: ["update"] - resourceNames: [""] - ``` - diff --git a/docs/drafts/provided-serviceaccount.md b/docs/drafts/provided-serviceaccount.md deleted file mode 100644 index 33f4501e9b..0000000000 --- a/docs/drafts/provided-serviceaccount.md +++ /dev/null @@ -1,31 +0,0 @@ -# Provided ServiceAccount for ClusterExtension Installation and Management - -Adhering to OLM v1's "Secure by Default" tenet, OLM v1 does not have the permissions -necessary to install content. This follows the least privilege principle and reduces -the chance of a [confused deputy attack](https://en.wikipedia.org/wiki/Confused_deputy_problem). -Instead, users must explicitly specify a ServiceAccount that will be used to perform the -installation and management of a specific ClusterExtension. The ServiceAccount is specified -in the ClusterExtension manifest as follows: - -```yaml -apiVersion: olm.operatorframework.io/v1alpha1 -kind: ClusterExtension -metadata: - name: argocd -spec: - source: - sourceType: Catalog - catalog: - packageName: argocd-operator - version: 0.6.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer -``` - -The ServiceAccount must be configured with the RBAC permissions required by the ClusterExtension. -If the permissions do not meet the minimum requirements, installation will fail. If no ServiceAccount -is provided in the ClusterExtension manifest, then the manifest will be rejected. - -//TODO: Add link to documentation on determining least privileges required for the ServiceAccount \ No newline at end of file diff --git a/docs/drafts/refs/olmv1-limitations.md b/docs/drafts/refs/olmv1-limitations.md deleted file mode 100644 index 1c351f9e91..0000000000 --- a/docs/drafts/refs/olmv1-limitations.md +++ /dev/null @@ -1,3 +0,0 @@ -# Current OLM v1 Limitations - -Placeholder. We need to document this. \ No newline at end of file diff --git a/docs/drafts/support-watchNamespaces.md b/docs/drafts/support-watchNamespaces.md deleted file mode 100644 index b10c279cc3..0000000000 --- a/docs/drafts/support-watchNamespaces.md +++ /dev/null @@ -1,24 +0,0 @@ -# Install Modes and WatchNamespaces in OMLv1 - -Operator Lifecycle Manager (OLM) operates with cluster-admin privileges, enabling it to grant necessary permissions to the Extensions it deploys. For extensions packaged as [`RegistryV1`][registryv1] bundles, it's the responsibility of the authors to specify supported `InstallModes` in the ClusterServiceVersion ([CSV][csv]). InstallModes define the operational scope of the extension within the Kubernetes cluster, particularly in terms of namespace availability. The four recognized InstallModes are as follows: - -1. OwnNamespace: This mode allows the extension to monitor and respond to events within its own deployment namespace. -1. SingleNamespace: In this mode, the extension is set up to observe events in a single, specific namespace other than the one it is deployed in. -1. MultiNamespace: This enables the extension to function across multiple specified namespaces. -1. AllNamespaces: Under this mode, the extension is equipped to monitor events across all namespaces within the cluster. - -When creating a cluster extension, users have the option to define a list of `watchNamespaces`. This list determines the specific namespaces within which they intend the operator to operate. The configuration of `watchNamespaces` must align with the InstallModes supported by the extension as specified by the bundle author. The supported configurations in the order of preference are as follows: - - -| Length of `watchNamespaces` specified through ClusterExtension | Allowed values | Supported InstallMode in CSV | Description | -|------------------------------|-------------------------------------------------------|----------------------|-----------------------------------------------------------------| -| **0 (Empty/Unset)** | - | AllNamespaces | Extension monitors all namespaces. | -| | - | OwnNamespace | Supported when `AllNamespaces` is false. Extension only active in its deployment namespace. | -| **1 (Single Entry)** | `""` (Empty String) | AllNamespaces | Extension monitors all namespaces. | -| | Entry equals Install Namespace | OwnNamespace | Extension watches only its install namespace. | -| | Entry is a specific namespace (not the Install Namespace) | SingleNamespace | Extension monitors a single, specified namespace in the spec. | -| **>1 (Multiple Entries)** | Entries are specific, multiple namespaces | MultiNamespace | Extension monitors each of the specified multiple namespaces in the spec. - - -[registryv1]: https://olm.operatorframework.io/docs/tasks/creating-operator-manifests/#writing-your-operator-manifests -[csv]: https://olm.operatorframework.io/docs/concepts/crds/clusterserviceversion/ \ No newline at end of file diff --git a/docs/getting-started/olmv1_getting_started.md b/docs/getting-started/olmv1_getting_started.md new file mode 100644 index 0000000000..de6f35f147 --- /dev/null +++ b/docs/getting-started/olmv1_getting_started.md @@ -0,0 +1,113 @@ +### Installation + +The following script will install OLMv1 on a Kubernetes cluster. If you don't have one, you can deploy a Kubernetes cluster with [KIND](https://sigs.k8s.io/kind). + +!!! warning + Operator-Controller depends on [cert-manager](https://cert-manager.io/). Running the following command + may affect an existing installation of cert-manager and cause cluster instability. + +The latest version of Operator Controller can be installed with the following command: + +```bash +curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s +``` + +### Getting Started with OLM v1 + +This quickstart procedure will guide you through the following processes: + +* Deploying a catalog +* Installing, upgrading, or downgrading an extension +* Deleting catalogs and extensions + +### Create a Catalog + +OLM v1 is designed to source content from an on-cluster catalog in the file-based catalog ([FBC](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs)) format. +These catalogs are deployed and configured through the `ClusterCatalog` resource. More information on adding catalogs +can be found [here](../tutorials/add-catalog.md). + +The following example uses the official [OperatorHub](https://operatorhub.io) catalog that contains many different +extensions to choose from. Note that this catalog contains packages designed to work with OLM v0, and that not all packages +will work with OLM v1. More information on catalog exploration and content compatibility can be found [here](../howto/catalog-queries.md). + +To create the catalog, run the following command: + +```bash +# Create ClusterCatalog +kubectl apply -f - < +``` +`` +: One of the `jq` queries from this document + +## Package queries + +* Available packages in a catalog: + ``` terminal + jq -s '.[] | select( .schema == "olm.package")' + ``` + +* Packages that support `AllNamespaces` install mode and do not use webhooks: + ``` terminal + jq -cs '[.[] | select(.schema == "olm.bundle" and (.properties[] | select(.type == "olm.csv.metadata").value.installModes[] | select(.type == "AllNamespaces" and .supported == true)) and .spec.webhookdefinitions == null) | .package] | unique[]' + ``` + +* Package metadata: + ``` terminal + jq -s '.[] | select( .schema == "olm.package") | select( .name == "")' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Catalog blobs in a package: + ``` terminal + jq -s '.[] | select( .package == "")' + ``` + + `` + : Name of the package from the catalog you are querying. + +## Channel queries + +* Channels in a package: + ``` terminal + jq -s '.[] | select( .schema == "olm.channel" ) | select( .package == "") | .name' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Versions in a channel: + ``` terminal + jq -s '.[] | select( .package == "" ) | select( .schema == "olm.channel" ) | select( .name == "" ) | .entries | .[] | .name' + ``` + + `` + : Name of the package from the catalog you are querying. + + `` + : Name of the channel for a given package. + +* Latest version in a channel and upgrade path: + ``` terminal + jq -s '.[] | select( .schema == "olm.channel" ) | select ( .name == "") | select( .package == "")' + ``` + + `` + : Name of the package from the catalog you are querying. + + `` + : Name of the channel for a given package. + +## Bundle queries + +* Bundles in a package: + ``` terminal + jq -s '.[] | select( .schema == "olm.bundle" ) | select( .package == "") | .name' + ``` + + `` + : Name of the package from the catalog you are querying. + +* Bundle dependencies and available APIs: + ``` terminal + jq -s '.[] | select( .schema == "olm.bundle" ) | select ( .name == "") | select( .package == "")' + ``` + + `` + : Name of the package from the catalog you are querying. + + `` + : Name of the bundle for a given package. diff --git a/docs/howto/derive-service-account.md b/docs/howto/derive-service-account.md new file mode 100644 index 0000000000..c1c1204da2 --- /dev/null +++ b/docs/howto/derive-service-account.md @@ -0,0 +1,357 @@ +# Derive minimal ServiceAccount required for ClusterExtension Installation and Management + +OLM v1 does not have permission to install extensions on a cluster by default. In order to install a [supported bundle](../project/olmv1_limitations.md), +OLM must be provided a ServiceAccount configured with the appropriate permissions. + +This document serves as a guide for how to derive the RBAC necessary to install a bundle. + +## Required RBAC + +The required permissions for the installation and management of a cluster extension can be determined by examining the contents of its bundle image. +This bundle image contains all the manifests that make up the extension (e.g. `CustomResourceDefinition`s, `Service`s, `Secret`s, `ConfigMap`s, `Deployment`s etc.) +as well as a [`ClusterServiceVersion`](https://olm.operatorframework.io/docs/concepts/crds/clusterserviceversion/) (CSV) that describes the extension and its service account's permission requirements. + +The service account must have permissions to: + + - create and manage the extension's `CustomResourceDefinition`s + - create and manage the resources packaged in the bundle + - grant the extension controller's service account the permissions it requires for its operation + - create and manage the extension controller's service account + - create and manage the `Role`s, `RoleBinding`s, `ClusterRole`s, and `ClusterRoleBinding`s associated with the extension controller's service account + - create and manage the extension controller's deployment + +Additionally, for clusters that use the [OwnerReferencesPermissionEnforcement](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement) admission plug-in, the service account must also have permissions to: + + - update finalizers on the ClusterExtension to be able to set blockOwnerDeletion and ownerReferences + +It is good security practice to follow the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), and scope permissions to specific resource names, wherever possible. +Keep in mind, that it is not possible to scope `create`, `list`, and `watch` permissions to specific resource names. + +Depending on the scope, each permission will need to be added to either a `ClusterRole` or a `Role` and then bound to the service account with a `ClusterRoleBinding` or a `RoleBinding`. + +## Example + +The following example illustrates the process of deriving the minimal RBAC required to install the [ArgoCD Operator](https://operatorhub.io/operator/argocd-operator) [v0.6.0](https://operatorhub.io/operator/argocd-operator/alpha/argocd-operator.v0.6.0) provided by [OperatorHub.io](https://operatorhub.io/). +The final permission set can be found in the [ClusterExtension sample manifest](https://github.com/operator-framework/operator-controller/blob/main/config/samples/olm_v1_clusterextension.yaml) in the [samples](https://github.com/operator-framework/operator-controller/blob/main/config/samples/olm_v1_clusterextension.yaml) directory. + +The bundle includes the following manifests, which can be found [here](https://github.com/argoproj-labs/argocd-operator/tree/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0): + +* `ClusterServiceVersion`: + - [argocd-operator.v0.6.0.clusterserviceversion.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml) +* `CustomResourceDefinition`s: + - [argoproj.io_applicationsets.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_applicationsets.yaml) + - [argoproj.io_applications.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_applications.yaml) + - [argoproj.io_appprojects.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_appprojects.yaml) + - [argoproj.io_argocdexports.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocdexports.yaml) + - [argoproj.io_argocds.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argoproj.io_argocds.yaml) +* Additional resources: + - [argocd-operator-controller-manager-metrics-service_v1_service.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator-controller-manager-metrics-service_v1_service.yaml) + - [argocd-operator-manager-config_v1_configmap.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator-manager-config_v1_configmap.yaml) + - [argocd-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml) + +The `ClusterServiceVersion` defines a single `Deployment` in `spec.install.deployments` named `argocd-operator-controller-manager` with a `ServiceAccount` of the same name. +It declares the following cluster-scoped permissions in `spec.install.clusterPermissions`, and its namespace-scoped permissions in `spec.install.permissions`. + +### Derive permissions for the installer service account `ClusterRole` + +#### Step 1. RBAC creation and management permissions + +The installer service account must create and manage the `ClusterRole`s and `ClusterRoleBinding`s for the extension controller(s). +Therefore, it must have the following permissions: + +```yaml +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [create, list, watch] +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [get, update, patch, delete] + resourceNames: [, ...] +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: [create, list, watch] +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: [get, update, patch, delete] + resourceNames: [, ...] +``` + +!!! note + The `resourceNames` field should be populated with the names of the `ClusterRole`s and `ClusterRoleBinding`s created by OLM v1. + These names are generated with the following format: `.`. Since it is not a trivial task + to generate these names ahead of time, it is recommended to use a wildcard `*` in the `resourceNames` field for the installation. + Then, update the `resourceNames` fields by inspecting the cluster for the generated resource names. For instance, for `ClusterRole`s: + +```terminal +kubectl get clusterroles | grep argocd +``` + +Example output: + +```terminal +argocd-installer-clusterrole 2024-09-30T08:02:09Z +argocd-installer-rbac-clusterrole 2024-09-30T08:02:09Z +argocd-operator-metrics-reader 2024-09-30T08:02:12Z +# The following are the generated ClusterRoles +argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx 2024-09-30T08:02:12Z +argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 2024-09-30T08:02:12Z +``` + +The same can be done for `ClusterRoleBindings`. + +#### Step 2. `CustomResourceDefinition` permissions + +The installer service account must be able to create and manage the `CustomResourceDefinition`s for the extension, as well +as grant the extension controller's service account the permissions it needs to manage its CRDs. + +```yaml +- apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [create, list, watch] +- apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [get, update, patch, delete] + # Scoped to the CRDs in the bundle + resourceNames: [applications.argoproj.io, appprojects.argoproj.io, argocds.argoproj.io, argocdexports.argoproj.io, applicationsets.argoproj.io] +``` + +#### Step 3. `OwnerReferencesPermissionEnforcement` permissions + +For clusters that use `OwnerReferencesPermissionEnforcement`, the installer service account must be able to update finalizers on the ClusterExtension to be able to set blockOwnerDeletion and ownerReferences for clusters that use `OwnerReferencesPermissionEnforcement`. +This is only a requirement for clusters that use the [OwnerReferencesPermissionEnforcement](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement) admission plug-in. + +```yaml +- apiGroups: [olm.operatorframework.io] + resources: [clusterextensions/finalizers] + verbs: [update] + # Scoped to the name of the ClusterExtension + resourceNames: [argocd-operator.v0.6.0] +``` + +#### Step 4. Bundled cluster-scoped resource permissions + +Permissions must be added for the creation and management of any cluster-scoped resources included in the bundle. +In this example, the ArgoCD bundle contains a `ClusterRole` called `argocd-operator-metrics-reader`. Given that +`ClusterRole` permissions have already been created in [Step 1](#step-1-rbac-creation-and-management-permissions), it +is sufficient to add the `argocd-operator-metrics-reader`resource name to the `resourceName` list of the pre-existing rule: + +```yaml +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [get, update, patch, delete] + resourceNames: [, ..., argocd-operator-metrics-reader] +``` + +#### Step 5. Operator permissions declared in the ClusterServiceVersion + +Include all permissions defined in the `.spec.install.permissions` ([reference](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L1091)) and `.spec.install.clusterPermissions` ([reference](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L872)) stanzas in the bundle's `ClusterServiceVersion`. +These permissions are required by the extension controller, and therefore the installer service account must be able to grant them. + +!!! note + There may be overlap between the rules defined in each stanza. Overlapping rules needn't be added twice. + +```yaml +# from .spec.install.clusterPermissions +- apiGroups: [""] + resources: ["configmaps", "endpoints", "events", "namespaces", "persistentvolumeclaims", "pods", "secrets", "serviceaccounts", "services", "services/finalizers"] + verbs: ["*"] +- apiGroups: [""] + resources: ["pods", "pods/log"] + verbs: ["get"] +- apiGroups: ["apps"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] + verbs: ["*"] +- apiGroups: ["apps"] + resourceNames: ["argocd-operator"] + resources: ["deployments/finalizers"] + verbs: ["update"] +- apiGroups: ["apps.openshift.io"] + resources: ["deploymentconfigs"] + verbs: ["*"] +- apiGroups: ["argoproj.io"] + resources: ["applications", "appprojects"] + verbs: ["*"] +- apiGroups: ["argoproj.io"] + resources: ["argocdexports", "argocdexports/finalizers", "argocdexports/status"] + verbs: ["*"] +- apiGroups: ["argoproj.io"] + resources: ["argocds", "argocds/finalizers", "argocds/status"] + verbs: ["*"] +- apiGroups: ["autoscaling"] + resources: ["horizontalpodautoscalers"] + verbs: ["*"] +- apiGroups: ["batch"] + resources: ["cronjobs", "jobs"] + verbs: ["*"] +- apiGroups: ["config.openshift.io"] + resources: ["clusterversions"] + verbs: ["get", "list", "watch"] +- apiGroups: ["monitoring.coreos.com"] + resources: ["prometheuses", "servicemonitors"] + verbs: ["*"] +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["*"] +- apiGroups: ["oauth.openshift.io"] + resources: ["oauthclients"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["*"] + verbs: ["*"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterrolebindings", "clusterroles"] + verbs: ["*"] +- apiGroups: ["route.openshift.io"] + resources: ["routes", "routes/custom-host"] + verbs: ["*"] +- apiGroups: ["template.openshift.io"] + resources: ["templateconfigs", "templateinstances", "templates"] + verbs: ["*"] +- apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["create"] +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] + +# copied from .spec.install.permissions +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +# overlapping permissions: +# - apiGroups: [""] +# resources: ["configmaps"] +# verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +# - apiGroups: [""] +# resources: ["events"] +# verbs: ["create", "patch"] +``` + +### Derive permissions for the installer service account `Role` + +The following steps detail how to define the namespace-scoped permissions needed by the installer service account's `Role`. +The installer service account must create and manage the `RoleBinding`s for the extension controller(s). + +#### Step 1. `Deployment` permissions + +The installer service account must be able to create and manage the `Deployment`s for the extension controller(s). +The `Deployment` name(s) can be found in the `ClusterServiceVersion` resource packed in the bundle under `.spec.install.deployments` ([reference](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml#L1029)). +This example's `ClusterServiceVersion` can be found [here](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml). + +```yaml +- apiGroups: [apps] + resources: [deployments] + verbs: [create] +- apiGroups: [apps] + resources: [deployments] + verbs: [get, list, watch, update, patch, delete] + # scoped to the extension controller deployment name + resourceNames: [argocd-operator-controller-manager] +``` + +#### Step 2: `ServiceAccount` permissions + +The installer service account must be able to create and manage the `ServiceAccount`(s) for the extension controller(s). +The `ServiceAccount` name(s) can be found in deployment template in the `ClusterServiceVersion` resource packed in the bundle under `.spec.install.deployments`. +This example's `ClusterServiceVersion` can be found [here](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator.v0.6.0.clusterserviceversion.yaml). + +```yaml +- apiGroups: [""] + resources: [serviceaccounts] + verbs: [create, list, watch] +- apiGroups: [""] + resources: [serviceaccounts] + verbs: [get, update, patch, delete] + # scoped to the extension controller's deployment service account + resourceNames: [argocd-operator-controller-manager] +``` + +#### Step 3. Bundled namespace-scoped resource permissions + +The installer service account must also create and manage other namespace-scoped resources included in the bundle. +In this example, the bundle also includes two additional namespace-scoped resources: + + * the [argocd-operator-controller-manager-metrics-service](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator-controller-manager-metrics-service_v1_service.yaml) `Service`, and + * the [argocd-operator-manager-config](https://github.com/argoproj-labs/argocd-operator/blob/da6b8a7e68f71920de9545152714b9066990fc4b/deploy/olm-catalog/argocd-operator/0.6.0/argocd-operator-manager-config_v1_configmap.yaml) `ConfigMap` + +Therefore, the following permissions must be given to the installer service account: + +```yaml +- apiGroups: [""] + resources: [services] + verbs: [create] +- apiGroups: [""] + resources: [services] + verbs: [get, list, watch, update, patch, delete] + # scoped to the service name + resourceNames: [argocd-operator-controller-manager-metrics-service] +- apiGroups: [""] + resources: [configmaps] + verbs: [create] +- apiGroups: [""] + resources: [configmaps] + verbs: [get, list, watch, update, patch, delete] + # scoped to the configmap name + resourceNames: [argocd-operator-manager-config] +``` + +### Putting it all together + +Once the installer service account required cluster-scoped and namespace-scoped permissions have been collected: + +1. Create the installation namespace +2. Create the installer `ServiceAccount` +3. Create the installer `ClusterRole` +4. Create the `ClusterRoleBinding` between the installer service account and its cluster role +5. Create the installer `Role` +6. Create the `RoleBinding` between the installer service account and its role +7. Create the `ClusterExtension` + +A manifest with the full set of resources can be found [here](https://github.com/operator-framework/operator-controller/blob/main/config/samples/olm_v1_clusterextension.yaml). + +## Alternatives + +We understand that manually determining the minimum RBAC required for installation/upgrade of a `ClusterExtension` quite complex and protracted. +In the near future, OLM v1 will provide tools and automation in order to simplify this process while maintaining our security posture. +For users wishing to test out OLM v1 in a non-production settings, we offer the following alternatives: + +### Give the installer service account admin privileges + +The `cluster-admin` `ClusterRole` can be bound to the installer service account giving it full permissions to the cluster. +While this obviates the need to determine the minimal RBAC required for installation, it is also dangerous. It is highly recommended +that this alternative only be used in test clusters. Never in production. + +Below is an example ClusterRoleBinding using the cluster-admin ClusterRole: + +```terminal +# Create ClusterRole +kubectl apply -f - < -o yaml +``` + +A user can use label selectors to find CRDs owned by a specific cluster extension: + +```bash +kubectl get crds -l 'olm.operatorframework.io/owner-kind=ClusterExtension,olm.operatorframework.io/owner-name=' +``` + +--- + +## 2. Creating Default ClusterRoles for API/CRD Access + +Administrators can define standard roles to control access to the API resources provided by installed cluster extensions. If the cluster extension does not provide default roles, you can create them yourself. + +### Default Roles + +- **View ClusterRole**: Grants read-only access to all custom resource objects of specified API resources across the cluster. This role is intended for users who need visibility into the resources without any permissions to modify them. It’s ideal for monitoring purposes and limited access viewing. +- **Edit ClusterRole**: Allows users to modify all custom resource objects within the cluster. This role enables users to create, update, and delete resources, making it suitable for team members who need to manage resources but should not control RBAC or manage permissions for others. +- **Admin ClusterRole**: Provides full permissions (create, update, delete) over all custom resource objects for the specified API resources across the cluster. + +### Example: Defining a Custom "View" ClusterRole + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: custom-resource-view +rules: +- apiGroups: + - + resources: + - + verbs: + - get + - list + - watch +``` + +### Example: Defining a Custom "Edit" ClusterRole + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: custom-resource-edit +rules: +- apiGroups: + - + resources: + - + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +``` + +### Example: Defining a Custom "Admin" ClusterRole + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: custom-resource-admin +rules: +- apiGroups: + - + resources: + - + verbs: + - '*' +``` + +!!! note + The `'*'` in verbs allows all actions on the specified resources. + In each case, replace `` and `` with the actual API group and resource names provided by the installed cluster extension. + +--- + +## 3. Granting User Access to API Resources + +Once the roles are created, you can bind them to specific users or groups to grant them the necessary permissions. There are two main ways to do this: + +### Option 1: Binding Default ClusterRoles to Users + +- **ClusterRoleBinding**: Use this to grant access across all namespaces. +- **RoleBinding**: Use this to grant access within a specific namespace. + +#### Example: ClusterRoleBinding + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: custom-resource-view-binding +subjects: +- kind: User + name: # Or use Group for group-based binding +roleRef: + kind: ClusterRole + name: custom-resource-view + apiGroup: rbac.authorization.k8s.io +``` + +This binding grants `` read-only access to the custom resource across all namespaces. + +#### Example: RoleBinding + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: custom-resource-edit-binding + namespace: +subjects: +- kind: User + name: +roleRef: + kind: Role + name: custom-resource-edit + apiGroup: rbac.authorization.k8s.io +``` + +This RoleBinding restricts permissions to a specific namespace. + +### Option 2: Extending Default Kubernetes Roles + +To automatically extend existing Kubernetes roles (e.g., the default `view`, `edit`, and `admin` roles), you can add **aggregation labels** to **ClusterRoles**. This allows users who already have `view`, `edit`, or `admin` roles to interact with the custom resource without needing additional RoleBindings. + +#### Example: Adding Aggregation Labels to a ClusterRole + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: custom-resource-aggregated-view + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" +rules: + - apiGroups: + - + resources: + - + verbs: + - get + - list + - watch +``` + +You can create similar ClusterRoles for `edit` and `admin` with appropriate verbs (such as `create`, `update`, `delete` for `edit` and `admin`). By using aggregation labels, the permissions for the custom resources are added to the default roles. + +> **Source**: [Kubernetes RBAC Aggregation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings) diff --git a/docs/drafts/how-to-pin-version.md b/docs/howto/how-to-pin-version.md similarity index 67% rename from docs/drafts/how-to-pin-version.md rename to docs/howto/how-to-pin-version.md index 17bd7e1c61..6072582f4a 100644 --- a/docs/drafts/how-to-pin-version.md +++ b/docs/howto/how-to-pin-version.md @@ -1,24 +1,23 @@ -## How-to: Version Pin and Disable Automatic Updates +# Pin Version and Disable Automatic Updates To disable automatic updates, and pin the version of an extension, set `version` in the Catalog source to a specific version (e.g. 1.2.3). Example: ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: argocd spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator version: 0.6.0 # Pin argocd-operator to v0.6.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer ``` -For more information on SemVer version ranges see [version ranges](version-ranges.md) +For more information on SemVer version ranges see [version ranges](../concepts/version-ranges.md) diff --git a/docs/drafts/how-to-version-range-upgrades.md b/docs/howto/how-to-version-range-upgrades.md similarity index 67% rename from docs/drafts/how-to-version-range-upgrades.md rename to docs/howto/how-to-version-range-upgrades.md index 9a5c305ee4..58a2161b6b 100644 --- a/docs/drafts/how-to-version-range-upgrades.md +++ b/docs/howto/how-to-version-range-upgrades.md @@ -1,24 +1,23 @@ -## How-to: Version Range Automatic Updates +# Version Range Automatic Updates Set the version for the desired package in the Catalog source to a comparison string, like `">=3.0, <3.6"`, to restrict the automatic updates to the version range. Any new version of the extension released in the catalog within this range will be automatically applied. Example: ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: argocd spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator version: ">=3.0, <3.6" # Install versions from v3.0.0 up to, but not including, v3.6.0 - install: - namespace: argocd - serviceAccount: - name: argocd-installer ``` -For more information on SemVer version ranges see [version-rages](version-ranges.md) \ No newline at end of file +For more information on SemVer version ranges see [version-ranges](../concepts/version-ranges.md) diff --git a/docs/drafts/how-to-z-stream-upgrades.md b/docs/howto/how-to-z-stream-upgrades.md similarity index 61% rename from docs/drafts/how-to-z-stream-upgrades.md rename to docs/howto/how-to-z-stream-upgrades.md index 835abc2b55..6ba440d2f9 100644 --- a/docs/drafts/how-to-z-stream-upgrades.md +++ b/docs/howto/how-to-z-stream-upgrades.md @@ -1,24 +1,23 @@ -## How-to: Z-Stream Automatic Updates +# Z-Stream Automatic Updates To restrict automatic updates to only z-stream patches and avoid breaking changes, use the `"~"` version range operator when setting the version for the desired package in Catalog source. Example: ```yaml -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterExtension metadata: name: argocd spec: + namespace: argocd + serviceAccount: + name: argocd-installer source: sourceType: Catalog catalog: packageName: argocd-operator - version: “~2.3" # Automatically upgrade patch releases for v2.3 - install: - namespace: argocd - serviceAccount: - name: argocd-installer + version: "~2.3" # Automatically upgrade patch releases for v2.3 ``` -For more information on SemVer version ranges see [version ranges](version-ranges.md) +For more information on SemVer version ranges see [version ranges](../concepts/version-ranges.md) diff --git a/docs/index.md b/docs/index.md index 6fda98519f..5676692072 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,49 +1,48 @@ -# What is Operator Lifecycle Manager (OLM)? +--- +hide: + - toc +--- -Operator Lifecycle Manager (OLM) is an open-source [CNCF](https://www.cncf.io/) project with the mission to manage the -lifecycle of cluster extensions centrally and declaratively on Kubernetes clusters. Its purpose is to make installing, -running, and updating functional extensions to the cluster easy, safe, and reproducible for cluster administrators and PaaS administrators. +# Operator Lifecycle Manager -Previously, OLM was focused on a particular type of cluster extension: [Operators](https://operatorhub.io/what-is-an-operator#:~:text=is%20an%20Operator-,What%20is%20an%20Operator%20after%20all%3F,or%20automation%20software%20like%20Ansible.). -Operators are a method of packaging, deploying, and managing a Kubernetes application. An Operator is composed of one or more controllers paired with one or both of the following objects: -* One or more API extensions -* One or more [CustomResourceDefinitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) (CRDs). +The Operator Lifecycle Manager (OLM) is an open-source project under the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/), designed to simplify and centralize the management of Kubernetes cluster extensions. OLM streamlines the process of installing, running, and updating these extensions, making it easier, safer, and more reproducible for cluster and platform administrators alike. -OLM helped define lifecycles for these extensions: from packaging and distribution to installation, configuration, upgrade, and removal. +Originally, OLM was focused on managing a specific type of extension known as [Operators](https://operatorhub.io/what-is-an-operator#:~:text=is%20an%20Operator-,What%20is%20an%20Operator%20after%20all%3F,or%20automation%20software%20like%20Ansible.), which are powerful tools that automate the management of complex Kubernetes applications. At its core, an Operator is made up of controllers that automate the lifecycle of applications, paired with: -The first iteration of OLM, termed OLM v0, included several concepts and features targeting the stability, security, and supportability of the life-cycled applications, for instance: -* A dependency model that enabled cluster extensions to focus on their primary purpose by delegating out of scope behavior to dependencies -* A constraint model that allowed cluster extension developers to define support limitations such as conflicting extensions, and minimum kubernetes versions -* A namespace-based multi-tenancy model in lieu of namespace-scoped CRDs -* A packaging model in which catalogs of extensions, usually containing the entire version history of each extension, are made available to clusters for cluster users to browse and select from +- One or more Kubernetes API extensions. +- One or more [CustomResourceDefinitions (CRDs)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), allowing administrators to define custom resources. -Since its initial release, OLM has helped catalyse the growth of Operators throughout the Kubernetes ecosystem. [OperatorHub.io](https://operatorhub.io/) -is a popular destination for discovering Operators, and boasts over 300 packages from many different vendors. +The purpose of OLM is to manage the lifecycle of these extensions—from their packaging and distribution to installation, updates, and eventual removal—helping administrators ensure stability and security across their clusters. -# Why are we building OLM v1? +In its first release (OLM v0), the project introduced several important concepts and features aimed at improving the lifecycle management of Kubernetes applications: -OLM v0 has been in production for over 5 years, and the community to leverage this experience and question the initial -goals and assumptions of the project. OLM v1 is a complete redesign and rewrite of OLM taking into account this accumulated experience. -Compared to its predecessor, amongst other things, OLM v1 aims to provide: -* A simpler API surface and mental model -* Less opinionated automation and greater flexibility -* Support for Kubernetes applications beyond only Operators -* Security by default -* Helm Chart support -* GitOps support +- **Dependency Model**: Enables extensions to focus on their primary function by delegating non-essential tasks to other dependencies. +- **Constraint Model**: Allows developers to define compatibility constraints such as conflicting extensions or minimum required Kubernetes versions. +- **Namespace-Based Multi-Tenancy**: Provides a multi-tenancy model to manage multiple extensions without the need for namespace-scoped CRDs. +- **Packaging Model**: Distributes extensions through catalogs, allowing users to browse and install extensions, often with access to the full version history. -For an in-depth look at OLM v1, please see the [OLM v1 Overview](olmv1_overview.md) and the [Roadmap](olmv1_roadmap.md). +Thanks to these innovations, OLM has played a significant role in popularizing Operators throughout the Kubernetes ecosystem. A prime example of its impact is [OperatorHub.io](https://operatorhub.io/), a widely-used platform with over 300 Operators from various vendors, providing users with a central location to discover and install Operators. -# The OLM community +## Why Build OLM v1? -In this next iteration of OLM, the community has also taken care to make it as contributor-friendly as possible, and welcomes new contributors. -The project is tracked in a [GitHub project](https://github.com/orgs/operator-framework/projects/8/), -which provides a great entry point to quickly find something interesting to work on and contribute. +After five years of real-world use, OLM has become an essential part of managing Kubernetes Operators. However, over time, the community has gathered valuable insights, uncovering both the strengths and limitations of OLM v0. These findings have led to a comprehensive redesign and the creation of OLM v1, with several key improvements over the initial version: -You can reach out to the OLM community for feedbacks/discussions/contributions in the following channels: +- **Simpler API and Mental Model**: Streamlined APIs and a more intuitive design, making it easier to understand and work with. +- **Greater Flexibility**: Less rigid automation, allowing for more customization and broader use cases. +- **Beyond Operators**: Support for a wider range of Kubernetes applications, not limited to Operators. +- **Security by Default**: Enhanced security features out-of-the-box, reducing vulnerabilities. +- **Helm Chart and GitOps Support**: Expanded support for popular Kubernetes tools like Helm and GitOps, broadening the range of integration options. - * Kubernetes Slack channel: [#olm-dev](https://kubernetes.slack.com/messages/olm-dev) - * [Operator Framework on Google Groups](https://groups.google.com/forum/#!forum/operator-framework) - * Weekly in-person Working Group meeting: [olm-wg](https://github.com/operator-framework/community#operator-lifecycle-manager-working-group) +For more details on the evolution of OLM and the roadmap for v1, explore the following resources: -For further information on contributing, please consult the [Contribution Guide](../CONTRIBUTING.md) +- [Multi-Tenancy Challenges, Lessons Learned, and Design Shifts](project/olmv1_design_decisions.md) +- [OLM v1 Roadmap](project/olmv1_roadmap.md) + +## Can I Migrate from OLMv0 to OLMv1? + +There is currently no concrete migration strategy due to the [conceptual differences between OLMv0 and OLMv1](project/olmv1_design_decisions.md). +OLMv1, as of writing, supports a subset of the existing content supported by OLMv0. +For more information regarding the current limitations of OLMv1, see [limitations](project/olmv1_limitations.md). + +If your current usage of OLMv0 is compatible with the limitations and expectations of OLMv1, you may be able to manually +transition to using OLMv1 following the standard workflows we have documented. diff --git a/docs/project/olmv1_architecture.md b/docs/project/olmv1_architecture.md new file mode 100644 index 0000000000..09889cc8db --- /dev/null +++ b/docs/project/olmv1_architecture.md @@ -0,0 +1,100 @@ +--- +hide: + - toc +--- + +## OLM v1 Architecture + +This document provides an overview of the architecture of OLM v1, which consists of two primary components: + +1. [operator-controller](https://github.com/operator-framework/operator-controller) +2. [catalogD](https://github.com/operator-framework/operator-controller/tree/main/catalogd) + +The diagram below visually represents the architecture of OLM v1, followed by descriptions of each component and its role within the system. + +### Architecture Diagram + +```mermaid +flowchart TB + A(bundle) + B(registry repo) + C(catalog author) + D(bundle author) + E(file based catalog) + F(extension controller) + G(bundle cache) + H(catalog cache) + I(resolver) + J(catalog controller) + K(catalog content cache) + L(catalog http server) + + subgraph Image-registry + B + end + subgraph Cluster + subgraph Operator-controller + F-->G + F-->I + I-->H + end + subgraph Catalogd + J-->K + L<-->K + end + end + + F-->L + F-->B + J-->B + C -- creates --> E + E -- pushed to --> B + D -- creates --> A + A -- pushed to --> B +``` + +!!! note + The direction of the arrows represents the flow of communication. If an arrow starts from A and points to B, it indicates that A retrieves or consumes information from B, unless otherwise specified. + +--- + +### operator-controller + +The `operator-controller` is the core component of OLM v1. Its responsibilities include: + +- Managing a cache of catalog metadata provided by catalogD through its HTTP server. +- Ensuring the catalog metadata cache is kept up-to-date with the latest catalog state. +- Identifying the appropriate `registry+v1` bundle that meets the constraints defined in the `ClusterExtension` resource (e.g., package name, version, channel) based on the cluster's current state. +- Unpacking and applying bundle manifests (e.g., installing or updating the operator). + +The operator-controller has three key sub-components: + +1. **Cluster Extension Controller**: + - Queries catalogD (via its HTTP server) to retrieve catalog information. + - Saves catalog information in the catalog cache and automatically updates the cache if a catalog has a new image reference. + - Downloads bundle container images from the registry, saves them to the bundle cache, unpacks them, and applies the bundle manifests to the cluster. + - Handles bundle upgrades by determining which bundle is the correct one to apply. + +2. **Resolver**: + - Assists the Cluster Extension Controller by filtering bundle references according to user-defined restrictions (e.g., package name, priority). It returns the filtered bundle reference to the extension controller. + +3. **Bundle Cache**: + - Stores previously unpacked bundles. If a bundle is not already cached, it downloads and caches it for future use. + +--- + +### catalogd + +catalogd is responsible for unpacking [file-based catalog (FBC)](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs) content, which is packaged and delivered as container images. It allows on-cluster clients to discover installable content by providing access to this metadata. In the future, catalogD will also support other sources like Git repositories and OCI artifacts. + +catalogd has three main sub-components: + +1. **ClusterCatalog Controller**: + - Pulls FBC-based catalog images from the registry and unpacks them into the catalog content cache. + - Reconciles any changes in the catalog and ensures the latest content is reflected in the cluster. + +2. **CatalogD HTTP Server**: + - Serves catalog information to clients, such as the Cluster Extension Controller. + +3. **CatalogD Content Cache**: + - A cache maintained by the Catalog Controller that stores unpacked catalog data, which the CatalogD HTTP Server uses to respond to client queries. diff --git a/docs/project/olmv1_community.md b/docs/project/olmv1_community.md new file mode 100644 index 0000000000..55e8cf1b85 --- /dev/null +++ b/docs/project/olmv1_community.md @@ -0,0 +1,15 @@ + +OLM is an open-source [CNCF](https://www.cncf.io/) project with a friendly and supportive community of developers, testers, + and documentation experts with a passion for Kubernetes. + +Through the effort of redesigning OLM, the community also took the opportunity to make the project more accessible, +and contributor-friendly through its weekly meetings, continuous planning, and a [GitHub project](https://github.com/orgs/operator-framework/projects/8/) + tracker that provides a convenient entry point to quickly find something interesting to work on and contribute. + +You can reach out to the OLM community for feedbacks/discussions/contributions in the following channels: + +* Kubernetes Slack channel: [#olm-dev](https://kubernetes.slack.com/messages/olm-dev) +* [Operator Framework on Google Groups](https://groups.google.com/forum/#!forum/operator-framework) +* Weekly in-person Working Group meeting: [olm-wg](https://github.com/operator-framework/community#operator-lifecycle-manager-working-group) + +For further information on contributing, please consult the [Contribution Guide](../contribute/contributing.md) \ No newline at end of file diff --git a/docs/olmv1_overview.md b/docs/project/olmv1_design_decisions.md similarity index 71% rename from docs/olmv1_overview.md rename to docs/project/olmv1_design_decisions.md index 417f6d5baf..6051bc2d8c 100644 --- a/docs/olmv1_overview.md +++ b/docs/project/olmv1_design_decisions.md @@ -1,48 +1,56 @@ -# OLM v1 Overview +# Multi-Tenancy Challenges, Lessons Learned, and Design Shifts -## What won't OLMv1 do that OLMv0 did? +This provides historical context on the design explorations and challenges that led to substantial design shifts between +OLM v1 and its predecessor. It explains the technical reasons why OLM v1 cannot support major v0 features, such as, +multi-tenancy, and namespace-specific controller configurations. Finally, it highlights OLM v1’s shift toward +more secure, predictable, and simple operations while moving away from some of the complex, error-prone features of OLM v0. -TL;DR: OLMv1 cannot feasibly support multi-tenancy or any feature that assumes multi-tenancy. All multi-tenancy features end up falling over because of the global API system of Kubernetes. While this short conclusion may be unsatisfying, the reasons are complex and intertwined. +## What won't OLM v1 do that OLM v0 did? + +TL;DR: OLM v1 cannot feasibly support multi-tenancy or any feature that assumes multi-tenancy. All multi-tenancy features end up falling over because of the global API system of Kubernetes. While this short conclusion may be unsatisfying, the reasons are complex and intertwined. ### Historical Context Nearly every active contributor in the Operator Framework project contributed to design explorations and prototypes over an entire year. For each of these design explorations, there are complex webs of features and assumptions that are necessary to understand the context that ultimately led to a conclusion of infeasibility. Here is a sampling of some of the ideas we explored: -- [[WIP] OLM v1's approach to multi-tenancy](https://docs.google.com/document/d/1xTu7XadmqD61imJisjnP9A6k38_fiZQ8ThvZSDYszog/edit#heading=h.m19itc78n5rw) -- [OLMv1 Multi-tenancy Brainstorming](https://docs.google.com/document/d/1ihFuJR9YS_GWW4_p3qjXu3WjvK0NIPIkt0qGixirQO8/edit#heading=h.vy9860qq1j01) + +- [OLM v1's approach to multi-tenancy](https://docs.google.com/document/d/1xTu7XadmqD61imJisjnP9A6k38_fiZQ8ThvZSDYszog/edit#heading=h.m19itc78n5rw) +- [OLM v1 Multi-tenancy Brainstorming](https://docs.google.com/document/d/1ihFuJR9YS_GWW4_p3qjXu3WjvK0NIPIkt0qGixirQO8/edit#heading=h.vy9860qq1j01) ### Watched namespaces cannot be configured in a first-class API -OLMv1 will not have a first-class API for configuring the namespaces that a controller will watch. +OLM v1 will not have a first-class API for configuring the namespaces that a controller will watch. Kubernetes APIs are global. Kubernetes is designed with the assumption that a controller WILL reconcile an object no matter where it is in the cluster. However, Kubernetes does not assume that a controller will be successful when it reconciles an object. The Kubernetes design assumptions are: + - CRDs and their controllers are trusted cluster extensions. - If an object for an API exists a controller WILL reconcile it, no matter where it is in the cluster. -OLMv1 will make the same assumption that Kubernetes does and that users of Kubernetes APIs do. That is: If a user has RBAC to create an object in the cluster, they can expect that a controller exists that will reconcile that object. If this assumption does not hold, it will be considered a configuration issue, not an OLMv1 bug. +OLM v1 will make the same assumption that Kubernetes does and that users of Kubernetes APIs do. That is: If a user has RBAC to create an object in the cluster, they can expect that a controller exists that will reconcile that object. If this assumption does not hold, it will be considered a configuration issue, not an OLM v1 bug. This means that it is a best practice to implement and configure controllers to have cluster-wide permission to read and update the status of their primary APIs. It does not mean that a controller needs cluster-wide access to read/write secondary APIs. If a controller can update the status of its primary APIs, it can tell users when it lacks permission to act on secondary APIs. ### Dependencies based on watched namespaces -Since there will be no first-class support for configuration of watched namespaces, OLMv1 cannot resolve dependencies among bundles based on where controllers are watching. +Since there will be no first-class support for configuration of watched namespaces, OLM v1 cannot resolve dependencies among bundles based on where controllers are watching. -However, not all bundle constraints are based on dependencies among bundles from different packages. OLMv1 will be able to support constraints against cluster state. For example, OLMv1 could support a “kubernetesVersionRange” constraint that blocks installation of a bundle if the current kubernetes cluster version does not fall into the specified range. +However, not all bundle constraints are based on dependencies among bundles from different packages. OLM v1 will be able to support constraints against cluster state. For example, OLM v1 could support a “kubernetesVersionRange” constraint that blocks installation of a bundle if the current kubernetes cluster version does not fall into the specified range. #### Background -For packages that specify API-based dependencies, OLMv0’s dependency checker knows which controllers are watching which namespaces. While OLMv1 will have awareness of which APIs are present on a cluster (via the discovery API), it will not know which namespaces are being watched for reconciliation of those APIs. Therefore dependency resolution based solely on API availability would only work in cases where controllers are configured to watch all namespaces. +For packages that specify API-based dependencies, OLMv0’s dependency checker knows which controllers are watching which namespaces. While OLM v1 will have awareness of which APIs are present on a cluster (via the discovery API), it will not know which namespaces are being watched for reconciliation of those APIs. Therefore dependency resolution based solely on API availability would only work in cases where controllers are configured to watch all namespaces. For packages that specify package-based dependencies, OLMv0’s dependency checker again knows which controllers are watching which namespaces. This case is challenging for a variety of reasons: + 1. How would a dependency resolver know which extensions were installed (let alone which extensions were watching which namespaces)? If a user is running the resolver, they would be blind to an installed extension that is watching their namespace if they don’t have permission to list extensions in the installation namespace. If a controller is running the resolver, then it might leak information to a user about installed extensions that the user is not otherwise entitled to know. 2. Even if (1) could be overcome, the lack of awareness of watched namespaces means that the resolver would have to make assumptions. If only one controller is installed, is it watching the right set of namespaces to meet the constraint? If multiple controllers are installed, are any of them watching the right set of namespaces? Without knowing the watched namespaces of the parent and child controllers, a correct dependency resolver implementation is not possible to implement. -Note that regardless of the ability of OLMv1 to perform dependency resolution (now or in the future), OLMv1 will not automatically install a missing dependency when a user requests an operator. The primary reasoning is that OLMv1 will err on the side of predictability and cluster-administrator awareness. +Note that regardless of the ability of OLM v1 to perform dependency resolution (now or in the future), OLM v1 will not automatically install a missing dependency when a user requests an operator. The primary reasoning is that OLM v1 will err on the side of predictability and cluster-administrator awareness. ### "Watch namespace"-aware operator discoverability @@ -50,7 +58,7 @@ When operators add APIs to a cluster, these APIs are globally visible. As stated Therefore, the API discoverability story boils down to answering this question for the user: “What APIs do I have access to in a given namespace?” Fortunately, built-in APIs exist to answer this question: Kubernetes Discovery, SelfSubjectRulesReview (SSRR), and SelfSubjectAccessReview (SSAR). -However, helping users discover which actual controllers will reconcile those APIs is not possible unless OLMv1 knows which namespaces those controllers are watching. +However, helping users discover which actual controllers will reconcile those APIs is not possible unless OLM v1 knows which namespaces those controllers are watching. Any solution here would be unaware of where a controller is actually watching and could only know “is there a controller installed that provides an implementation of this API?”. However even knowledge of a controller installation is not certain. Any user can use the discovery, SSRR, and SSAR. Not all users can list all Extensions (see [User discovery of “available” APIs](#user-discovery-of-available-apis)). @@ -59,19 +67,21 @@ Any solution here would be unaware of where a controller is actually watching an The multi-tenancy promises that OLMv0 made were false promises. Kubernetes is not multi-tenant with respect to management of APIs (because APIs are global). Any promise that OLMv0 has around multi-tenancy evaporates when true tenant isolation attempts are made, and any attempt to fix a broken promise is actually just a bandaid on an already broken assumption. So where do we go from here? There are multiple solutions that do not involve OLM implementing full multi-tenancy support, some or all of which can be explored. + 1. Customers transition to a control plane per tenant 2. Extension authors update their operators to support customers’ multi-tenancy use cases 3. Extension authors with “simple” lifecycling concerns transition to other packaging and deployment strategies (e.g. helm charts) ### Single-tenant control planes -One choice for customers would be to adopt low-overhead single-tenant control planes in which every tenant can have full control over their APIs and controllers and be truly isolated (at the control plane layer at least) from other tenants. With this option, the things OLMv1 cannot do (listed above) are irrelevant, because the purpose of all of those features is to support multi-tenant control planes in OLM. +One choice for customers would be to adopt low-overhead single-tenant control planes in which every tenant can have full control over their APIs and controllers and be truly isolated (at the control plane layer at least) from other tenants. With this option, the things OLM v1 cannot do (listed above) are irrelevant, because the purpose of all of those features is to support multi-tenant control planes in OLM. The [Kubernetes multi-tenancy docs](https://kubernetes.io/docs/concepts/security/multi-tenancy/#virtual-control-plane-per-tenant) contain a good overview of the options in this space. Kubernetes vendors may also have their own virtual control plane implementations. ### Shift multi-tenant responsibility to operators There is a set of operators that both (a) provide fully namespace-scoped workload-style operands and that (b) provide a large amount of value to their users for advanced features like backup and migration. For these operators, the Operator Framework program would suggest that they shift toward supporting multi-tenancy directly. That would involve: + 1. Taking extreme care to avoid API breaking changes. 2. Supporting multiple versions of their operands in a single version of the operator (if required by users in multi-tenant clusters). 3. Maintaining support for versioned operands for the same period of time that the operator is supported for a given cluster version. @@ -79,9 +89,9 @@ There is a set of operators that both (a) provide fully namespace-scoped workloa ### Operator authors ship controllers outside of OLM -Some projects have been successful delivering and supporting their operator on Kubernetes, but outside of OLM, for example with helm-packaged operators. On this path, individual layered project teams have more flexibility in solving lifecycling problems for their users because they are unencumbered by OLM’s opinions. However the tradeoff is that those project teams and their users take on responsibility and accountability for safe upgrades, automation, and multi-tenant architectures. With OLMv1 no longer attempting to support multi-tenancy in a first-class way, these tradeoffs change and project teams may decide that a different approach is necessary. +Some projects have been successful delivering and supporting their operator on Kubernetes, but outside of OLM, for example with helm-packaged operators. On this path, individual layered project teams have more flexibility in solving lifecycling problems for their users because they are unencumbered by OLM’s opinions. However the tradeoff is that those project teams and their users take on responsibility and accountability for safe upgrades, automation, and multi-tenant architectures. With OLM v1 no longer attempting to support multi-tenancy in a first-class way, these tradeoffs change and project teams may decide that a different approach is necessary. -This path does not necessarily mean a scattering of content in various places. It would still be possible to provide customers with a marketplace of content (e.g. see https://artifacthub.io/). +This path does not necessarily mean a scattering of content in various places. It would still be possible to provide customers with a marketplace of content (e.g. see [artifacthub.io](https://artifacthub.io/)). ### Authors of "simple" operators ship their workload without an operator @@ -110,10 +120,11 @@ OLM constantly monitors the state of all on-cluster resources for all the operat ### CRD Upgrade Safety Checks Before OLM upgrades a CRD, OLM performs a set of safety checks to identify any changes that potentially would have negative impacts, such as: + - data loss - incompatible schema changes -These checks may not be a guarantee that an upgrade is safe; instead, they are intended to provide an early warning sign for identifiable incompatibilities. False positives (OLMv1 claims a breaking change when there is none) and false negatives (a breaking change makes it through the check without being caught) are possible, at least while the OLMv1 team iterates on this feature. +These checks may not be a guarantee that an upgrade is safe; instead, they are intended to provide an early warning sign for identifiable incompatibilities. False positives (OLM v1 claims a breaking change when there is none) and false negatives (a breaking change makes it through the check without being caught) are possible, at least while the OLM v1 team iterates on this feature. ### User permissions management @@ -124,61 +135,66 @@ Also note that user permission management does not unlock operator discoverabili ### User discovery of “available” APIs In the future, the Operator Framework team could explore building an API similar to SelfSubjectAccessReview and SelfSubjectRulesReview that answers the question: -“What is the public metadata of all of the extensions that are installed on the cluster that provide APIs that I have permission for in namespace X?” +“What is the public metadata of all extensions that are installed on the cluster that provide APIs that I have permission for in namespace X?” One solution would be to join “installed extensions with user permissions”. If an installed extension provides an API that a user has RBAC permission for, that extension would be considered available to that user in that scope. This solution would not be foolproof: it makes the (reasonable) assumption that an administrator only configures RBAC for a user in a namespace where a controller is reconciling that object. If an administrator gives a user RBAC access to an API without also configuring that controller to watch the namespace that they have access to, the discovery solution would report an available extension, but then nothing would actually reconcile the object they create. This solution would tell users about API-only and API+controller bundles that are installed. It would not tell users about controller-only bundles, because they do not include APIs. -Other similar API-centric solutions could be explored as well. For example, pursuing enhancements to OLMv1 or core Kubernetes related to API metadata and/or grouping. +Other similar API-centric solutions could be explored as well. For example, pursuing enhancements to OLM v1 or core Kubernetes related to API metadata and/or grouping. -A key note here is that controller-specific metadata like the version of the controller that will reconcile the object in a certain namespace is not necessary for discovery. Discovery is primarily about driving user flows around presenting information and example usage of a group of APIs such that CLIs and UIs can provide rich experiences around interactions with available APIs. +A key insight here is that controller-specific metadata like the version of the controller that will reconcile the object in a certain namespace is not necessary for discovery. Discovery is primarily about driving user flows around presenting information and example usage of a group of APIs such that CLIs and UIs can provide rich experiences around interactions with available APIs. ## Approach -We will adhere to the following tenets in our approach for the design and implementation of OLMv1 +We will adhere to the following tenets in our approach for the design and implementation of OLM v1 ### Do not fight Kubernetes One of the key features of cloud-native applications/extensions/operators is that they typically come with a Kubernetes-based API (e.g. CRD) and a controller that reconciles instances of that API. In Kubernetes, API registration is cluster-scoped. It is not possible to register different APIs in different namespaces. Instances of an API can be cluster- or namespace-scoped. All APIs are global (they can be invoked/accessed regardless of namespace). For cluster-scoped APIs, the names of their instances must be unique. For example, it’s possible to have Nodes named “one” and “two”, but it’s not possible to have multiple Nodes named “two”. For namespace-scoped APIs, the names of their instances must be unique per namespace. The following illustrates this for ConfigMaps, a namespace-scoped API: Allowed + - Namespace: test, name: my-configmap - Namespace: other, name: my-configmap Disallowed + - Namespace: test, name: my-configmap - Namespace: test, name: my-configmap In cases where OLMv0 decides that joint ownership of CRDs will not impact different tenants, OLMv0 allows multiple installations of bundles that include the same named CRD, and OLMv0 itself manages the CRD lifecycle. This has security implications because it requires OLMv0 to act as a deputy, but it also pits OLM against the limitations of the Kubernetes API. OLMv0 promises that different versions of an operator can be installed in the cluster for use by different tenants without tenants being affected by each other. This is not a promise OLM can make because it is not possible to have multiple versions of the same CRD present on a cluster for different tenants. -In OLMv1, we will not design the core APIs and controllers around this promise. Instead, we will build an API where ownership of installed objects is not shared. Managed objects are owned by exactly one extension. +In OLM v1, we will not design the core APIs and controllers around this promise. Instead, we will build an API where ownership of installed objects is not shared. Managed objects are owned by exactly one extension. This pattern is generic, aligns with the Kubernetes API, and makes multi-tenancy a possibility, but not a guarantee or core concept. We will explore the implications of this design on existing OLMv0 registry+v1 bundles as part of the larger v0 to v1 migration design. For net new content, operator authors that intend multiple installations of operator on the same cluster would need to package their components to account for this ownership rule. Generally, this would entail separation along these lines: + - CRDs, conversion webhook workloads, and admission webhook configurations and workloads, APIServices and workloads. - Controller workloads, service accounts, RBAC, etc. -OLMv1 will include primitives (e.g. templating) to make it possible to have multiple non-conflicting installations of bundles. +OLM v1 will include primitives (e.g. templating) to make it possible to have multiple non-conflicting installations of bundles. -However it should be noted that the purpose of these primitives is not to enable multi-tenancy. It is to enable administrators to provide configuration for the installation of an extension. The fact that operators can be packaged as separate bundles and parameterized in a way that permits multiple controller installations is incidental, and not something that OLMv1 will encourage or promote. +However, it should be noted that the purpose of these primitives is not to enable multi-tenancy. It is to enable administrators to provide configuration for the installation of an extension. The fact that operators can be packaged as separate bundles and parameterized in a way that permits multiple controller installations is incidental, and not something that OLM v1 will encourage or promote. ### Make OLM secure by default OLMv0 runs as cluster-admin, which is a security concern. OLMv0 has optional security controls for operator installations via the OperatorGroup, which allows a user with permission to create or update them to also set a ServiceAccount that will be used for authorization purposes on operator installations and upgrades in that namespace. If a ServiceAccount is not explicitly specified, OLM’s cluster-admin credentials are used. Another avenue that cluster administrators have is to lock down permissions and usage of the CatalogSource API, disable default catalogs, and provide tenants with custom vetted catalogs. However if a cluster admin is not aware of these options, the default configuration of a cluster results in users with permission to create a Subscription in namespaces that contain an OperatorGroup effectively have cluster-admin, because OLMv0 has unlimited permissions to install any bundle available in the default catalogs and the default community catalog is not vetted for limited RBAC. Because OLMv0 is used to install more RBAC and run arbitrary workloads, there are numerous potential vectors that attackers could exploit. While there are no known exploits and there has not been any specific concern reported from customers, we believe CNCF’s reputation rest on secure cloud-native software and that this is a non-negotiable area to improve. To make OLM secure by default: -- OLMv1 will not be granted cluster admin permissions. Instead it will require service accounts provided by users to actually install, upgrade, and delete content. In addition to the security this provides, it also fulfills one of OLM’s long-standing requirements: halt when bundle upgrades require additional permissions and wait until those permissions are granted. -- OLMv1 will use secure communication protocols between all internal components and between itself and its clients. + +- OLM v1 will not be granted cluster admin permissions. Instead, it will require service accounts provided by users to actually install, upgrade, and delete content. In addition to the security this provides, it also fulfills one of OLM’s long-standing requirements: halt when bundle upgrades require additional permissions and wait until those permissions are granted. +- OLM v1 will use secure communication protocols between all internal components and between itself and its clients. ### Simple and predictable semantics for install, upgrade, and delete OLMv0 has grown into a complex web of functionality that is difficult to understand, even for seasoned Kubernetes veterans. -In OLMv1 we will move to GitOps-friendly APIs that allow administrators to rely on their experience with conventional Kubernetes API behavior (declarative, eventually consistent) to manage operator lifecycles. +In OLM v1 we will move to GitOps-friendly APIs that allow administrators to rely on their experience with conventional Kubernetes API behavior (declarative, eventually consistent) to manage operator lifecycles. + +OLM v1 will reduce its API surface down to two primary APIs that represent catalogs of content, and intent for that content to be installed on the cluster. -OLMv1 will reduce its API surface down to two primary APIs that represent catalogs of content, and intent for that content to be installed on the cluster. +OLM v1 will: -OLMv1 will: - Permit administrators to pin to specific versions, channels, version ranges, or combinations of both. - Permit administrators to pause management of an installation for maintenance or troubleshooting purposes. - Put opinionated guardrails up by default (e.g. follow operator developer-defined upgrade edges). @@ -188,56 +204,60 @@ OLMv1 will: ### APIs and behaviors to handle common controller patterns OLMv0 takes an extremely opinionated stance on the contents of the bundles it installs and in the way that operators can be lifecycled. The original designers believed these opinions would keep OLM’s scope limited and that they encompassed best practices for operator lifecycling. Some of these opinions are: + - All bundles must include a ClusterServiceVersion, which ostensibly gives operator authors an API that they can use to fully describe how to run the operator, what permissions it requires, what APIs it provides, and what metadata to show to users. - Bundles cannot contain arbitrary objects. OLMv0 needs to have specific handling for each resource that it supports. - Cluster administrators cannot override OLM safety checks around CRD changes or upgrades. -OLMv1 will take a slightly different approach: -- It will not require bundles to have very specific controller-centric shapes. OLMv1 will happily install a bundle that contains a deployment, service, and ingress or a bundle that contains a single configmap. +OLM v1 will take a slightly different approach: + +- It will not require bundles to have very specific controller-centric shapes. OLM v1 will happily install a bundle that contains a deployment, service, and ingress or a bundle that contains a single configmap. - However for bundles that do include CRDs, controllers, RBAC, webhooks, and other objects that relate to the behavior of the apiserver, OLM will continue to have opinions and special handling: - - CRD upgrade checks (best effort) - - Specific knowledge and handling of webhooks. -- To the extent necessary OLMv1 will include optional controller-centric concepts in its APIs and or CLIs in order to facilitate the most common controller patterns. Examples could include: - - Permission management - - CRD upgrade check policies -- OLMv1 will continue to have opinions about upgrade traversals and CRD changes that help users prevent accidental breakage, but it will also allow administrators to disable safeguards and proceed anyway. + - CRD upgrade checks (best effort) + - Specific knowledge and handling of webhooks. +- To the extent necessary OLM v1 will include optional controller-centric concepts in its APIs and or CLIs in order to facilitate the most common controller patterns. Examples could include: + - Permission management + - CRD upgrade check policies +- OLM v1 will continue to have opinions about upgrade traversals and CRD changes that help users prevent accidental breakage, but it will also allow administrators to disable safeguards and proceed anyway. -OLMv0 has some support for automatic upgrades. However administrators cannot control the maximum version for automatic upgrades, and the upgrade policy (manual vs automatic) applies to all operators in a namespace. If any operator’s upgrade policy is manual, all upgrades of all operators in the namespace must be approved manually. +OLMv0 has some support for automatic upgrades. However, administrators cannot control the maximum version for automatic upgrades, and the upgrade policy (manual vs automatic) applies to all operators in a namespace. If any operator’s upgrade policy is manual, all upgrades of all operators in the namespace must be approved manually. -OLMv1 will have fine-grained control for version ranges (and pins) and for controlling automatic upgrades for individual operators regardless of the policy of other operators installed in the same namespace. +OLM v1 will have fine-grained control for version ranges (and pins) and for controlling automatic upgrades for individual operators regardless of the policy of other operators installed in the same namespace. ### Constraint checking (but not automated on-cluster management) OLMv0 includes support for dependency and constraint checking for many common use cases (e.g. required and provided APIs, required cluster version, required package versions). It also has other constraint APIs that have not gained traction (e.g. CEL expressions and compound constraints). In addition to its somewhat hap-hazard constraint expression support, OLMv0 also automatically installs dependency trees, which has proven problematic in several respects: + 1. OLMv0 can resolve existing dependencies from outside the current namespace, but it can only install new dependencies in the current namespace. One scenario where this is problematic is if A depends on B, where A supports only OwnNamespace mode and B supports only AllNamespace mode. In that case, OLMv0’s auto dependency management fails because B cannot be installed in the same namespace as A (assuming the OperatorGroup in that namespace is configured for OwnNamespace operators to work). 2. OLMv0’s logic for choosing a dependency among multiple contenders is confusing and error-prone, and an administrator’s ability to have fine-grained control of upgrades is essentially limited to building and deploying tailor-made catalogs. 3. OLMv0 automatically installs dependencies. The only way for an administrator to avoid this OLMv0 functionality is to fully understand the dependency tree in advance and to then install dependencies from the leaves to the root so that OLMv0 always detects that dependencies are already met. If OLMv0 installs a dependency for you, it does not uninstall it when it is no longer depended upon. -OLMv1 will not provide dependency resolution among packages in the catalog (see [Dependencies based on watched namespaces](#dependencies-based-on-watched-namespaces)) +OLM v1 will not provide dependency resolution among packages in the catalog (see [Dependencies based on watched namespaces](#dependencies-based-on-watched-namespaces)) -OLMv1 will provide constraint checking based on available cluster state. Constraint checking will be limited to checking whether the existing constraints are met. If so, install proceeds. If not, unmet constraints will be reported and the install/upgrade waits until constraints are met. +OLM v1 will provide constraint checking based on available cluster state. Constraint checking will be limited to checking whether the existing constraints are met. If so, install proceeds. If not, unmet constraints will be reported and the install/upgrade waits until constraints are met. -The Operator Framework team will perform a survey of registry+v1 packages that currently rely on OLMv0’s dependency features and will suggest a solution as part of the overall OLMv0 to OLMv1 migration effort. +The Operator Framework team will perform a survey of registry+v1 packages that currently rely on OLMv0’s dependency features and will suggest a solution as part of the overall OLMv0 to OLM v1 migration effort. ### Client libraries and CLIs contribute to the overall UX OLMv0 has no official client libraries or CLIs that can be used to augment its functionality or provide a more streamlined user experience. The kubectl "operator" plugin was developed several years ago, but has never been a focus of the core Operator Framework development team, and has never factored into the overall architecture. -OLMv1 will deliver an official CLI (likely by overhauling the kubectl operator plugin) and will use it to meet requirements that are difficult or impossible to implement in a controller, or where an architectural assessment dictates that a client is the better choice. This CLI would automate standard workflows over cluster APIs to facilitate simple administrative actions (e.g. automatically create RBAC and ServiceAccounts necessary for an extension installation as an optional step in the CLI’s extension install experience). +OLM v1 will deliver an official CLI (likely by overhauling the kubectl operator plugin) and will use it to meet requirements that are difficult or impossible to implement in a controller, or where an architectural assessment dictates that a client is the better choice. This CLI would automate standard workflows over cluster APIs to facilitate simple administrative actions (e.g. automatically create RBAC and ServiceAccounts necessary for an extension installation as an optional step in the CLI’s extension install experience). The official CLI will provide administrators and users with a UX that covers the most common scenarios users will encounter. -The official CLI will explicitly NOT attempt to cover complex scenarios. Maintainers will reject requests to over-complicate the CLI. Users with advanced use cases will be able to directly interact with OLMv1’s on-cluster APIs. +The official CLI will explicitly NOT attempt to cover complex scenarios. Maintainers will reject requests to over-complicate the CLI. Users with advanced use cases will be able to directly interact with OLM v1’s on-cluster APIs. The idea is: + - On-cluster APIs can be used to manage operators in 100% of cases (assuming bundle content is structured in a compatible way) - The official CLI will cover standard user flows, covering ~80% of use cases. - Third-party or unofficial CLIs will cover the remaining ~20% of use cases. Areas where the official CLI could provide value include: + - Catalog interactions (search, list, inspect, etc.) - Standard install/upgrade/delete commands - Upgrade previews - RBAC management - Discovery of available APIs - diff --git a/docs/project/olmv1_limitations.md b/docs/project/olmv1_limitations.md new file mode 100644 index 0000000000..01ce9436d3 --- /dev/null +++ b/docs/project/olmv1_limitations.md @@ -0,0 +1,23 @@ +--- +hide: + - toc +--- + +## Content Support + +Currently, OLM v1 only supports installing operators packaged in [OLM v0 bundles](https://olm.operatorframework.io/docs/tasks/creating-operator-bundle/) +, also known as `registry+v1` bundles. Additionally, the bundled operator, or cluster extension: + +* **must** support installation via the `AllNamespaces` install mode +* **must not** declare dependencies using any of the following file-based catalog properties: + * `olm.gvk.required` + * `olm.package.required` + * `olm.constraint` + +OLM v1 verifies these criteria at install time and will surface violations in the `ClusterExtensions`'s `.status.conditions`. + +!!! important + + OLM v1 does not support the `OperatorConditions` API introduced in legacy OLM. + + Currently, there is no testing to validate against this constraint. If an extension uses the `OperatorConditions` API, the extension does not install correctly. Most extensions that rely on this API fail at start time, but some might fail during reconcilation. diff --git a/docs/olmv1_roadmap.md b/docs/project/olmv1_roadmap.md similarity index 98% rename from docs/olmv1_roadmap.md rename to docs/project/olmv1_roadmap.md index 23bcc5d96e..ffcf94c425 100644 --- a/docs/olmv1_roadmap.md +++ b/docs/project/olmv1_roadmap.md @@ -1,10 +1,12 @@ --- -title: Product Requriement Doc -layout: default -nav_order: 2 +hide: + - toc --- -# OLM v1 roadmap +# OLM v1 roadmap (Deprecated) + +**Note**: The current roadmap is deprecated and it is not up-to-date. Refer to project [dashboard](https://github.com/orgs/operator-framework/projects/8/views/47) to get latest information. + ## Functional Requirements _Priority Rating: 1 highest, 2 medium, 3 lower (e.g. P2 = Medium Priority)_ @@ -154,7 +156,3 @@ OLM 1.0 does not support managing bundles or extension versions that do not supp - Migration scripting is provided to mass-convert existing installed extensions (“Subscription” / “OperatorGroup” objects) on existing clusters to the new OLM 1.0 model assuming they are compatible - Extension authors that are also SRE/Managed PaaS administrators are incentivized to make their extension compatible with the requirements of OLM 1.0 to reap the operational benefits - -# TODO -- Definition of "extension" -- Does OLM become ELM? Does this provide of provisioning bundles that do not add APIs? diff --git a/docs/project/public-api.md b/docs/project/public-api.md new file mode 100644 index 0000000000..687c14a2a5 --- /dev/null +++ b/docs/project/public-api.md @@ -0,0 +1,11 @@ +# Public API +The public API of OLM v1 is as follows: + +- Kubernetes APIs. For more information on these APIs, see: + - [OLMv1 API reference](../api-reference/olmv1-api-reference.md) +- `Catalogd` web server. For more information on what this includes, see the [catalogd web server documentation](../api-reference/catalogd-webserver.md) + +!!! warning + + Only what is mentioned in the above documentation references is considered part of the public API + and follows SemVer. Anything not mentioned in the above references is prone to breaking changes. diff --git a/docs/refs/api/operator-controller-api-reference.md b/docs/refs/api/operator-controller-api-reference.md deleted file mode 100644 index d03bc4df4e..0000000000 --- a/docs/refs/api/operator-controller-api-reference.md +++ /dev/null @@ -1,281 +0,0 @@ -# API Reference - -## Packages -- [olm.operatorframework.io/v1alpha1](#olmoperatorframeworkiov1alpha1) - - -## olm.operatorframework.io/v1alpha1 - -Package v1alpha1 contains API Schema definitions for the olm v1alpha1 API group - -### Resource Types -- [ClusterExtension](#clusterextension) -- [ClusterExtensionList](#clusterextensionlist) - - - -#### BundleMetadata - - - - - - - -_Appears in:_ -- [ClusterExtensionInstallStatus](#clusterextensioninstallstatus) -- [ClusterExtensionResolutionStatus](#clusterextensionresolutionstatus) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `name` _string_ | name is a required field and is a reference
to the name of a bundle | | | -| `version` _string_ | version is a required field and is a reference
to the version that this bundle represents | | | - - -#### CRDUpgradeSafetyPolicy - -_Underlying type:_ _string_ - - - - - -_Appears in:_ -- [CRDUpgradeSafetyPreflightConfig](#crdupgradesafetypreflightconfig) - -| Field | Description | -| --- | --- | -| `Enabled` | | -| `Disabled` | | - - -#### CRDUpgradeSafetyPreflightConfig - - - -CRDUpgradeSafetyPreflightConfig is the configuration for CRD upgrade safety preflight check. - - - -_Appears in:_ -- [PreflightConfig](#preflightconfig) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `policy` _[CRDUpgradeSafetyPolicy](#crdupgradesafetypolicy)_ | policy is used to configure the state of the CRD Upgrade Safety pre-flight check.

This field is required when the spec.install.preflight.crdUpgradeSafety field is
specified.

Allowed values are ["Enabled", "Disabled"]. The default value is "Enabled".

When set to "Disabled", the CRD Upgrade Safety pre-flight check will be skipped
when performing an upgrade operation. This should be used with caution as
unintended consequences such as data loss can occur.

When set to "Enabled", the CRD Upgrade Safety pre-flight check will be run when
performing an upgrade operation. | Enabled | Enum: [Enabled Disabled]
| - - -#### CatalogSource - - - -CatalogSource defines the required fields for catalog source. - - - -_Appears in:_ -- [SourceConfig](#sourceconfig) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `packageName` _string_ | packageName is a reference to the name of the package to be installed
and is used to filter the content from catalogs.

This field is required, immutable and follows the DNS subdomain name
standard as defined in [RFC 1123]. This means that valid entries:
- Contain no more than 253 characters
- Contain only lowercase alphanumeric characters, '-', or '.'
- Start with an alphanumeric character
- End with an alphanumeric character

Some examples of valid values are:
- some-package
- 123-package
- 1-package-2
- somepackage

Some examples of invalid values are:
- -some-package
- some-package-
- thisisareallylongpackagenamethatisgreaterthanthemaximumlength
- some.package

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
| -| `version` _string_ | version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed.

Acceptable version ranges are no longer than 64 characters.
Version ranges are composed of comma- or space-delimited values and one or
more comparison operators, known as comparison strings. Additional
comparison strings can be added using the OR operator (\|\|).

# Range Comparisons

To specify a version range, you can use a comparison string like ">=3.0,
<3.6". When specifying a range, automatic updates will occur within that
range. The example comparison string means "install any version greater than
or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any
upgrades are available within the version range after initial installation,
those upgrades should be automatically performed.

# Pinned Versions

To specify an exact version to install you can use a version range that
"pins" to a specific version. When pinning to a specific version, no
automatic updates will occur. An example of a pinned version range is
"0.6.0", which means "only install version 0.6.0 and never
upgrade from this version".

# Basic Comparison Operators

The basic comparison operators and their meanings are:
- "=", equal (not aliased to an operator)
- "!=", not equal
- "<", less than
- ">", greater than
- ">=", greater than OR equal to
- "<=", less than OR equal to

# Wildcard Comparisons

You can use the "x", "X", and "*" characters as wildcard characters in all
comparison operations. Some examples of using the wildcard characters:
- "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0"
- ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0"
- "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3"
- "x", "X", and "*" is equivalent to ">= 0.0.0"

# Patch Release Comparisons

When you want to specify a minor version up to the next major version you
can use the "~" character to perform patch comparisons. Some examples:
- "~1.2.3" is equivalent to ">=1.2.3, <1.3.0"
- "~1" and "~1.x" is equivalent to ">=1, <2"
- "~2.3" is equivalent to ">=2.3, <2.4"
- "~1.2.x" is equivalent to ">=1.2.0, <1.3.0"

# Major Release Comparisons

You can use the "^" character to make major release comparisons after a
stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples:
- "^1.2.3" is equivalent to ">=1.2.3, <2.0.0"
- "^1.2.x" is equivalent to ">=1.2.0, <2.0.0"
- "^2.3" is equivalent to ">=2.3, <3"
- "^2.x" is equivalent to ">=2.0.0, <3"
- "^0.2.3" is equivalent to ">=0.2.3, <0.3.0"
- "^0.2" is equivalent to ">=0.2.0, <0.3.0"
- "^0.0.3" is equvalent to ">=0.0.3, <0.0.4"
- "^0.0" is equivalent to ">=0.0.0, <0.1.0"
- "^0" is equivalent to ">=0.0.0, <1.0.0"

# OR Comparisons
You can use the "\|\|" character to represent an OR operation in the version
range. Some examples:
- ">=1.2.3, <2.0.0 \|\| >3.0.0"
- "^0 \|\| ^3 \|\| ^5"

For more information on semver, please see https://semver.org/ | | MaxLength: 64
Pattern: `^(\s*(=\|\|!=\|>\|<\|>=\|=>\|<=\|=<\|~\|~>\|\^)\s*(v?(0\|[1-9]\d*\|[x\|X\|\*])(\.(0\|[1-9]\d*\|x\|X\|\*]))?(\.(0\|[1-9]\d*\|x\|X\|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+\|,\s*\|\s*\\|\\|\s*)(=\|\|!=\|>\|<\|>=\|=>\|<=\|=<\|~\|~>\|\^)\s*(v?(0\|[1-9]\d*\|x\|X\|\*])(\.(0\|[1-9]\d*\|x\|X\|\*))?(\.(0\|[1-9]\d*\|x\|X\|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$`
| -| `channels` _string array_ | channels is an optional reference to a set of channels belonging to
the package specified in the packageName field.

A "channel" is a package author defined stream of updates for an extension.

When specified, it is used to constrain the set of installable bundles and
the automated upgrade path. This constraint is an AND operation with the
version field. For example:
- Given channel is set to "foo"
- Given version is set to ">=1.0.0, <1.5.0"
- Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable
- Automatic upgrades will be constrained to upgrade edges defined by the selected channel

When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths.

This field follows the DNS subdomain name standard as defined in [RFC
1123]. This means that valid entries:
- Contain no more than 253 characters
- Contain only lowercase alphanumeric characters, '-', or '.'
- Start with an alphanumeric character
- End with an alphanumeric character

Some examples of valid values are:
- 1.1.x
- alpha
- stable
- stable-v1
- v1-stable
- dev-preview
- preview
- community

Some examples of invalid values are:
- -some-channel
- some-channel-
- thisisareallylongchannelnamethatisgreaterthanthemaximumlength
- original_40
- --default-channel

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | | -| `selector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#labelselector-v1-meta)_ | selector is an optional field that can be used
to filter the set of ClusterCatalogs used in the bundle
selection process.

When unspecified, all ClusterCatalogs will be used in
the bundle selection process. | | | -| `upgradeConstraintPolicy` _[UpgradeConstraintPolicy](#upgradeconstraintpolicy)_ | upgradeConstraintPolicy is an optional field that controls whether
the upgrade path(s) defined in the catalog are enforced for the package
referenced in the packageName field.

Allowed values are: ["CatalogProvided", "SelfCertified"].

When this field is set to "CatalogProvided", automatic upgrades will only occur
when upgrade constraints specified by the package author are met.

When this field is set to "SelfCertified", the upgrade constraints specified by
the package author are ignored. This allows for upgrades and downgrades to
any version of the package. This is considered a dangerous operation as it
can lead to unknown and potentially disastrous outcomes, such as data
loss. It is assumed that users have independently verified changes when
using this option.

If unspecified, the default value is "CatalogProvided". | CatalogProvided | Enum: [CatalogProvided SelfCertified]
| - - -#### ClusterExtension - - - -ClusterExtension is the Schema for the clusterextensions API - - - -_Appears in:_ -- [ClusterExtensionList](#clusterextensionlist) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `apiVersion` _string_ | `olm.operatorframework.io/v1alpha1` | | | -| `kind` _string_ | `ClusterExtension` | | | -| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | -| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ClusterExtensionSpec](#clusterextensionspec)_ | | | | -| `status` _[ClusterExtensionStatus](#clusterextensionstatus)_ | | | | - - -#### ClusterExtensionInstallConfig - - - -ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config. -ClusterExtensionInstallConfig requires the namespace and serviceAccount which should be used for the installation of packages. - - - -_Appears in:_ -- [ClusterExtensionSpec](#clusterextensionspec) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `namespace` _string_ | namespace is a reference to the Namespace in which the bundle of
content for the package referenced in the packageName field will be applied.
The bundle may contain cluster-scoped resources or resources that are
applied to other Namespaces. This Namespace is expected to exist.

namespace is required, immutable, and follows the DNS label standard
as defined in [RFC 1123]. This means that valid values:
- Contain no more than 63 characters
- Contain only lowercase alphanumeric characters or '-'
- Start with an alphanumeric character
- End with an alphanumeric character

Some examples of valid values are:
- some-namespace
- 123-namespace
- 1-namespace-2
- somenamespace

Some examples of invalid values are:
- -some-namespace
- some-namespace-
- thisisareallylongnamespacenamethatisgreaterthanthemaximumlength
- some.namespace

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 63
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
| -| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a required reference to a ServiceAccount that exists
in the installNamespace. The provided ServiceAccount is used to install and
manage the content for the package specified in the packageName field.

In order to successfully install and manage the content for the package,
the ServiceAccount provided via this field should be configured with the
appropriate permissions to perform the necessary operations on all the
resources that are included in the bundle of content being applied. | | | -| `preflight` _[PreflightConfig](#preflightconfig)_ | preflight is an optional field that can be used to configure the preflight checks run before installation or upgrade of the content for the package specified in the packageName field.

When specified, it overrides the default configuration of the preflight checks that are required to execute successfully during an install/upgrade operation.

When not specified, the default configuration for each preflight check will be used. | | | - - -#### ClusterExtensionInstallStatus - - - - - - - -_Appears in:_ -- [ClusterExtensionStatus](#clusterextensionstatus) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `bundle` _[BundleMetadata](#bundlemetadata)_ | bundle is a representation of the currently installed bundle.

A "bundle" is a versioned set of content that represents the resources that
need to be applied to a cluster to install a package. | | | - - -#### ClusterExtensionList - - - -ClusterExtensionList contains a list of ClusterExtension - - - - - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `apiVersion` _string_ | `olm.operatorframework.io/v1alpha1` | | | -| `kind` _string_ | `ClusterExtensionList` | | | -| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | -| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | -| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `items` _[ClusterExtension](#clusterextension) array_ | | | | - - -#### ClusterExtensionResolutionStatus - - - - - - - -_Appears in:_ -- [ClusterExtensionStatus](#clusterextensionstatus) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `bundle` _[BundleMetadata](#bundlemetadata)_ | bundle is a representation of the bundle that was identified during
resolution to meet all installation/upgrade constraints and is slated to be
installed or upgraded to.

A "bundle" is a versioned set of content that represents the resources that
need to be applied to a cluster to install a package. | | | - - -#### ClusterExtensionSpec - - - -ClusterExtensionSpec defines the desired state of ClusterExtension - - - -_Appears in:_ -- [ClusterExtension](#clusterextension) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content
for this ClusterExtension. Selection is performed by setting the sourceType.

Catalog is currently the only implemented sourceType, and setting the
sourcetype to "Catalog" requires the catalog field to also be defined.

Below is a minimal example of a source definition (in yaml):

source:
sourceType: Catalog
catalog:
packageName: example-package | | | -| `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is a required field used to configure the installation options
for the ClusterExtension such as the installation namespace,
the service account and the pre-flight check configuration.

Below is a minimal example of an installation definition (in yaml):
install:
namespace: example-namespace
serviceAccount:
name: example-sa | | | - - -#### ClusterExtensionStatus - - - -ClusterExtensionStatus defines the observed state of ClusterExtension. - - - -_Appears in:_ -- [ClusterExtension](#clusterextension) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `install` _[ClusterExtensionInstallStatus](#clusterextensioninstallstatus)_ | | | | -| `resolution` _[ClusterExtensionResolutionStatus](#clusterextensionresolutionstatus)_ | | | | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | conditions is a representation of the current state for this ClusterExtension.
The status is represented by a set of "conditions".

Each condition is generally structured in the following format:
- Type: a string representation of the condition type. More or less the condition "name".
- Status: a string representation of the state of the condition. Can be one of ["True", "False", "Unknown"].
- Reason: a string representation of the reason for the current state of the condition. Typically useful for building automation around particular Type+Reason combinations.
- Message: a human readable message that further elaborates on the state of the condition

The global set of condition types are:
- "Installed", represents whether or not the a bundle has been installed for this ClusterExtension
- "Resolved", represents whether or not a bundle was found that satisfies the selection criteria outlined in the spec
- "Unpacked", represents whether or not the bundle contents have been successfully unpacked

When the ClusterExtension is sourced from a catalog, the following conditions are also possible:
- "Deprecated", represents an aggregation of the PackageDeprecated, ChannelDeprecated, and BundleDeprecated condition types
- "PackageDeprecated", represents whether or not the package specified in the spec.source.catalog.packageName field has been deprecated
- "ChannelDeprecated", represents whether or not any channel specified in spec.source.catalog.channels has been deprecated
- "BundleDeprecated", represents whether or not the installed bundle is deprecated

The current set of reasons are:
- "Success", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when unpacking a bundle's content, resolution and installation/upgrading is successful
- "Failed", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when an error has occurred while unpacking the contents of a bundle, during resolution or installation. | | | - - -#### PreflightConfig - - - -PreflightConfig holds the configuration for the preflight checks. If used, at least one preflight check must be non-nil. - - - -_Appears in:_ -- [ClusterExtensionInstallConfig](#clusterextensioninstallconfig) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `crdUpgradeSafety` _[CRDUpgradeSafetyPreflightConfig](#crdupgradesafetypreflightconfig)_ | crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight
checks that run prior to upgrades of installed content.

The CRD Upgrade Safety pre-flight check safeguards from unintended
consequences of upgrading a CRD, such as data loss.

This field is required if the spec.install.preflight field is specified. | | | - - -#### ServiceAccountReference - - - -ServiceAccountReference references a serviceAccount. - - - -_Appears in:_ -- [ClusterExtensionInstallConfig](#clusterextensioninstallconfig) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `name` _string_ | name is a required, immutable reference to the name of the ServiceAccount
to be used for installation and management of the content for the package
specified in the packageName field.

This ServiceAccount is expected to exist in the installNamespace.

This field follows the DNS subdomain name standard as defined in [RFC
1123]. This means that valid values:
- Contain no more than 253 characters
- Contain only lowercase alphanumeric characters, '-', or '.'
- Start with an alphanumeric character
- End with an alphanumeric character

Some examples of valid values are:
- some-serviceaccount
- 123-serviceaccount
- 1-serviceaccount-2
- someserviceaccount
- some.serviceaccount

Some examples of invalid values are:
- -some-serviceaccount
- some-serviceaccount-

[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
| - - -#### SourceConfig - - - -SourceConfig is a discriminated union which selects the installation source. - - - -_Appears in:_ -- [ClusterExtensionSpec](#clusterextensionspec) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `sourceType` _string_ | sourceType is a required reference to the type of install source.

Allowed values are ["Catalog"]

When this field is set to "Catalog", information for determining the appropriate
bundle of content to install will be fetched from ClusterCatalog resources existing
on the cluster. When using the Catalog sourceType, the catalog field must also be set. | | Enum: [Catalog]
| -| `catalog` _[CatalogSource](#catalogsource)_ | catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog",
and must be the only field defined for this sourceType. | | | - - -#### UpgradeConstraintPolicy - -_Underlying type:_ _string_ - - - - - -_Appears in:_ -- [CatalogSource](#catalogsource) - -| Field | Description | -| --- | --- | -| `CatalogProvided` | The extension will only upgrade if the new version satisfies
the upgrade constraints set by the package author.
| -| `SelfCertified` | Unsafe option which allows an extension to be
upgraded or downgraded to any available version of the package and
ignore the upgrade path designed by package authors.
This assumes that users independently verify the outcome of the changes.
Use with caution as this can lead to unknown and potentially
disastrous results such as data loss.
| - - diff --git a/docs/refs/catalog-queries.md b/docs/refs/catalog-queries.md deleted file mode 100644 index fda87987e3..0000000000 --- a/docs/refs/catalog-queries.md +++ /dev/null @@ -1,74 +0,0 @@ -# Catalog queries - -**Note:** By default, Catalogd is installed with TLS enabled for the catalog webserver. -The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag. - - -You can use the `curl` command with `jq` to query catalogs that are installed on your cluster. - -``` terminal title="Query syntax" -curl -k https://localhost:8443/catalogs/operatorhubio/all.json | -``` - - - -## Package queries - -Available packages in a catalog -: -``` terminal -jq -s '.[] | select( .schema == "olm.package") -``` - -Packages that support `AllNamespaces` install mode and do not use webhooks - -: -``` terminal -jq -c 'select(.schema == "olm.bundle") | {"package":.package, "version":.properties[] | select(.type == "olm.bundle.object").value.data | @base64d | fromjson | select(.kind == "ClusterServiceVersion" and (.spec.installModes[] | select(.type == "AllNamespaces" and .supported == true) != null) and .spec.webhookdefinitions == null).spec.version}' -``` - -Package metadata -: -``` terminal -jq -s '.[] | select( .schema == "olm.package") | select( .name == "")' -``` - -Catalog blobs in a package -: -``` terminal -jq -s '.[] | select( .package == "")' -``` - -## Channel queries - -Channels in a package -: -``` terminal -jq -s '.[] | select( .schema == "olm.channel" ) | select( .package == "") | .name' -``` - -Versions in a channel -: -``` terminal -jq -s '.[] | select( .package == "" ) | select( .schema == "olm.channel" ) | select( .name == "" ) | .entries | .[] | .name' -``` - -Latest version in a channel and upgrade path -: -``` terminal -jq -s '.[] | select( .schema == "olm.channel" ) | select ( .name == "") | select( .package == "")' -``` - -## Bundle queries - -Bundles in a package -: -``` terminal -jq -s '.[] | select( .schema == "olm.bundle" ) | select( .package == "") | .name' -``` - -Bundle dependencies and available APIs -: -``` terminal -jq -s '.[] | select( .schema == "olm.bundle" ) | select ( .name == "") | select( .package == "")' -``` diff --git a/docs/Tasks/adding-a-catalog.md b/docs/tutorials/add-catalog.md similarity index 53% rename from docs/Tasks/adding-a-catalog.md rename to docs/tutorials/add-catalog.md index 12d07adbf9..2d7bdfce26 100644 --- a/docs/Tasks/adding-a-catalog.md +++ b/docs/tutorials/add-catalog.md @@ -1,4 +1,9 @@ -# Adding a catalog of extensions to a cluster +--- +hide: + - toc +--- + +# Add a Catalog of Extensions to a Cluster Extension authors can publish their products in catalogs. ClusterCatalogs are curated collections of Kubernetes extensions, such as Operators. @@ -12,8 +17,7 @@ This catalog is distributed as an image [quay.io/operatorhubio/catalog](https:// ## Prerequisites * Access to a Kubernetes cluster, for example `kind`, using an account with `cluster-admin` permissions -* [Operator Controller installed](https://github.com/operator-framework/operator-controller/releases) on the cluster -* [Catalogd installed](https://github.com/operator-framework/catalogd/releases/) on the cluster +* [Operator Controller and Catalogd installed](https://github.com/operator-framework/operator-controller/releases) on the cluster * Kubernetes CLI (`kubectl`) installed on your workstation ## Procedure @@ -21,44 +25,42 @@ This catalog is distributed as an image [quay.io/operatorhubio/catalog](https:// 1. Create a catalog custom resource (CR): ``` yaml title="clustercatalog_cr.yaml" - apiVersion: olm.operatorframework.io/v1alpha1 + apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: - name: operatorhubio + name: spec: source: - type: image + type: Image image: ref: - pollInterval: + pollIntervalMinutes: ``` - `catalog_name` + `catalog_image` : Specifies the image reference for the catalog you want to install, such as `quay.io/operatorhubio/catalog:latest`. `poll_interval_duration` - : Specifies the interval for polling the remote registry for newer image digests. - The default value is `24h`. - Valid units include seconds (`s`), minutes (`m`), and hours (`h`). - To disable polling, set a zero value, such as `0s`. + : Specifies the number of minutes for polling the remote registry for newer image digests. + This field is optional. To disable polling, unset the field. ``` yaml title="Example `operatorhubio.yaml` CR" - apiVersion: olm.operatorframework.io/v1alpha1 + apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: - name: operatorhub + name: operatorhubio spec: source: - type: image + type: Image image: ref: quay.io/operatorhubio/catalog:latest - pollInterval: 1h + pollIntervalMinutes: 10 ``` 2. Apply the ClusterCatalog CR: ``` terminal - kubectl apply -f .yaml + kubectl apply -f operatorhubio.yaml ``` ``` text title="Example output" @@ -76,8 +78,8 @@ This catalog is distributed as an image [quay.io/operatorhubio/catalog](https:// ``` ``` terminal title="Example output" - NAME LASTUNPACKED AGE - operatorhubio 9m31s 9m55s + NAME LASTUNPACKED SERVING AGE + operatorhubio 18s True 27s ``` * Check the status of your catalog: @@ -89,37 +91,45 @@ This catalog is distributed as an image [quay.io/operatorhubio/catalog](https:// ``` terminal title="Example output" Name: operatorhubio Namespace: - Labels: + Labels: olm.operatorframework.io/metadata.name=operatorhubio Annotations: - API Version: olm.operatorframework.io/v1alpha1 + API Version: olm.operatorframework.io/v1 Kind: ClusterCatalog Metadata: - Creation Timestamp: 2024-03-12T19:34:50Z + Creation Timestamp: 2024-11-13T15:11:08Z Finalizers: olm.operatorframework.io/delete-server-cache - Generation: 2 - Resource Version: 6469 - UID: 2e2778cb-dda6-4645-96b7-992e8dd37503 + Generation: 1 + Resource Version: 3069 + UID: 2c94ebf8-32ea-4a62-811a-c7098cd2d4db Spec: + Availability Mode: Available + Priority: 0 Source: Image: - Poll Interval: 15m0s - Ref: quay.io/operatorhubio/catalog:latest - Type: image + Poll Interval Minutes: 10 + Ref: quay.io/operatorhubio/catalog:latest + Type: Image Status: Conditions: - Last Transition Time: 2024-03-12T19:35:34Z - Message: - Reason: UnpackSuccessful + Last Transition Time: 2024-11-13T15:11:19Z + Message: Successfully unpacked and stored content from resolved source + Observed Generation: 1 + Reason: Succeeded + Status: True + Type: Progressing + Last Transition Time: 2024-11-13T15:11:19Z + Message: Serving desired content from resolved source + Observed Generation: 1 + Reason: Available Status: True - Type: Unpacked - Content URL: https://catalogd-catalogserver.olmv1-system.svc/catalogs/operatorhubio/all.json - Observed Generation: 2 + Type: Serving + Last Unpacked: 2024-11-13T15:11:18Z Resolved Source: Image: - Last Poll Attempt: 2024-03-12T19:35:26Z - Ref: quay.io/operatorhubio/catalog:latest - Resolved Ref: quay.io/operatorhubio/catalog@sha256:dee29aaed76fd1c72b654b9bc8bebc4b48b34fd8d41ece880524dc0c3c1c55ec - Type: image - Events: + Ref: quay.io/operatorhubio/catalog@sha256:3cd8fde1dfd4269467451c4b2c77d4196b427004f2eb82686376f28265655c1c + Type: Image + Urls: + Base: https://catalogd-service.olmv1-system.svc/catalogs/operatorhubio + Events: ``` diff --git a/docs/tutorials/downgrade-extension.md b/docs/tutorials/downgrade-extension.md new file mode 100644 index 0000000000..57035f989d --- /dev/null +++ b/docs/tutorials/downgrade-extension.md @@ -0,0 +1,209 @@ +--- +hide: + - toc +--- + +# Downgrade a ClusterExtension + +## Introduction + +Downgrading a `ClusterExtension` involves reverting the extension to a previously available version. This process may be necessary due to compatibility issues, unexpected behavior in the newer version, or specific feature requirements only available in an earlier release. However, downgrading carries inherent risks, such as potential data loss, issues with new CRD versions, and possible breakage of clients that rely on the newer version. Users should carefully consider these risks and be confident in their decision to proceed with the downgrade. This guide provides step-by-step instructions for performing a downgrade, including overrides to bypass default constraints and disable CRD safety checks. + +## Prerequisites + +Before initiating the downgrade process, ensure the following prerequisites are met: + +- **Backup Configurations:** Always back up your current configurations and data to prevent potential loss during the downgrade. +- **Access Rights:** Ensure you have the necessary permissions to modify `ClusterExtension` resources and perform administrative tasks. +- **Version Availability:** Verify that the target downgrade version is available in your catalogs. +- **Compatibility Check:** Ensure that the target version is compatible with your current system and other dependencies. + +## Steps to Downgrade + +### 1. Disabling the CRD Upgrade Safety Check + +Custom Resource Definitions (CRDs) ensure that the resources used by the `ClusterExtension` are valid and consistent. During a downgrade, the CRD Upgrade Safety check might prevent reverting to an incompatible version. Disabling the CRD Upgrade Safety check allows the downgrade to proceed without these validations. + +**Disable CRD Safety Check Configuration:** + +Add the `crdUpgradeSafety` field and set its `enforcement` to `None` in the `ClusterExtension` resource. This configuration disables CRD safety checks during the downgrade process. + +**Example:** + +```yaml +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: example-extension +spec: + namespace: argocd + serviceAccount: + name: argocd-installer + install: + preflight: + crdUpgradeSafety: + enforcement: None + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 +``` + +**Command Example:** + +If you prefer using the command line, you can use `kubectl` to modify the upgrade CRD safety check configuration. + +```bash +kubectl patch clusterextension example-extension --patch '{"spec":{"install":{"preflight":{"crdUpgradeSafety":{"enforcement":"None"}}}}}' --type=merge +``` + +### 2. Ignoring Catalog Provided Upgrade Constraints + +By default, Operator Lifecycle Manager (OLM) enforces upgrade constraints based on semantic versioning and catalog definitions. To allow downgrades, you need to override these constraints. + +**Override Configuration:** + +Set the `upgradeConstraintPolicy` to `SelfCertified` in the `ClusterExtension` resource. This configuration permits downgrades, sidegrades, and any version changes without adhering to the predefined upgrade paths. + +**Example:** + +```yaml +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: example-extension +spec: + namespace: argocd + serviceAccount: + name: argocd-installer + install: + preflight: + crdUpgradeSafety: + enforcement: None + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 + upgradeConstraintPolicy: SelfCertified +``` + +**Command Example:** + +If you prefer using the command line, you can use `kubectl` to modify the upgrade constraint policy. + +```bash +kubectl patch clusterextension example-extension --patch '{"spec":{"source": {"catalog":{"upgradeConstraintPolicy":"SelfCertified"}}}}' --type=merge +``` + +### 3. Executing the Downgrade + +Once the CRD safety checks are disabled and upgrade constraints are set, you can proceed with the actual downgrade. + +1. **Edit the ClusterExtension Resource:** + + Modify the `ClusterExtension` custom resource to specify the target version and adjust the upgrade constraints. + + ```bash + kubectl edit clusterextension example-extension + ``` + +2. **Update the Version:** + + Within the YAML editor, update the `spec` section as follows: + + ```yaml + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: example-extension + spec: + namespace: argocd + serviceAccount: + name: argocd-installer + install: + preflight: + crdUpgradeSafety: + enforcement: None + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: + upgradeConstraintPolicy: SelfCertified + ``` + + `target_version` + : Specify the target version you wish to downgrade to. + +3. **Apply the Changes:** + + Save and exit the editor. Kubernetes will apply the changes and initiate the downgrade process. + +### 4. Post-Downgrade Verification + +After completing the downgrade, verify that the `ClusterExtension` is functioning as expected. + +**Verification Steps:** + +1. **Check the Status of the ClusterExtension:** + + ```bash + kubectl get clusterextension example-extension -o yaml + ``` + + Ensure that the `status` reflects the target version and that there are no error messages. + +2. **Validate CRD Integrity:** + + Confirm that all CRDs associated with the `ClusterExtension` are correctly installed and compatible with the downgraded version. + + ```bash + kubectl get crd | grep + ``` + +3. **Test Extension Functionality:** + + Perform functional tests to ensure that the extension operates correctly in its downgraded state. + +4. **Monitor Logs:** + + Check the logs of the operator managing the `ClusterExtension` for any warnings or errors. + + ```bash + kubectl logs deployment/ -n + ``` + +## Troubleshooting + +During the downgrade process, you might encounter issues. Below are common problems and their solutions: + +### Downgrade Fails Due to Version Constraints + +**Solution:** + +- Ensure that the `upgradeConstraintPolicy` is set to `SelfCertified`. +- Verify that the target version exists in the catalog. +- Check for typos or incorrect version numbers in the configuration. + +### CRD Compatibility Issues + +**Solution:** + +- Review the changes in CRDs between versions to ensure compatibility. +- If disabling the CRD safety check, ensure that the downgraded version can handle the existing CRDs without conflicts. +- Consider manually reverting CRDs if necessary, but proceed with caution to avoid data loss. + +### Extension Becomes Unresponsive After Downgrade + +**Solution:** + +- Restore from the backup taken before the downgrade. +- Investigate logs for errors related to the downgraded version. +- Verify that all dependencies required by the downgraded version are satisfied. + +## Additional Resources + +- [Semantic Versioning Specification](https://semver.org/) +- [Manually Verified Upgrades and Downgrades](https://github.com/operator-framework/operator-controller/blob/main/docs/drafts/upgrade-support.md#manually-verified-upgrades-and-downgrades) diff --git a/docs/tutorials/explore-available-content.md b/docs/tutorials/explore-available-content.md new file mode 100644 index 0000000000..36e3cf8834 --- /dev/null +++ b/docs/tutorials/explore-available-content.md @@ -0,0 +1,152 @@ +--- +hide: + - toc +--- + +# Explore Available Content + +After you [add a catalog of extensions](add-catalog.md) to your cluster, you must port forward your catalog as a service. +Then you can query the catalog by using `curl` commands and the `jq` CLI tool to find extensions to install. + +## Prerequisites + +* You have added a ClusterCatalog of extensions, such as [OperatorHub.io](https://operatorhub.io), to your cluster. +* You have installed the `jq` CLI tool. + +!!! note + By default, Catalogd is installed with TLS enabled for the catalog webserver. + The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag. + +## Procedure + +1. Port forward the catalog server service: + + ``` terminal + kubectl -n olmv1-system port-forward svc/catalogd-service 8443:443 + ``` + +2. Return a list of all the extensions in a catalog via the v1 API endpoint: + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.package") | .name' + ``` + + ??? success + ``` text title="Example output" + "ack-acm-controller" + "ack-acmpca-controller" + "ack-apigatewayv2-controller" + "ack-applicationautoscaling-controller" + "ack-cloudfront-controller" + "ack-cloudtrail-controller" + "ack-cloudwatch-controller" + "ack-cloudwatchlogs-controller" + "ack-dynamodb-controller" + "ack-ec2-controller" + "ack-ecr-controller" + "ack-ecs-controller" + "ack-efs-controller" + "ack-eks-controller" + "ack-elasticache-controller" + "ack-emrcontainers-controller" + "ack-eventbridge-controller" + "ack-iam-controller" + "ack-kafka-controller" + "ack-keyspaces-controller" + "ack-kinesis-controller" + "ack-kms-controller" + "ack-lambda-controller" + "ack-memorydb-controller" + "ack-mq-controller" + "ack-networkfirewall-controller" + "ack-opensearchservice-controller" + "ack-pipes-controller" + "ack-prometheusservice-controller" + "ack-rds-controller" + "ack-route53-controller" + "ack-route53resolver-controller" + "ack-s3-controller" + "ack-sagemaker-controller" + "ack-secretsmanager-controller" + "ack-sfn-controller" + "ack-sns-controller" + "ack-sqs-controller" + "aerospike-kubernetes-operator" + "airflow-helm-operator" + "aiven-operator" + "akka-cluster-operator" + "alvearie-imaging-ingestion" + "anchore-engine" + "apch-operator" + "api-operator" + "api-testing-operator" + "apicast-community-operator" + "apicurio-registry" + "apimatic-kubernetes-operator" + "app-director-operator" + "appdynamics-operator" + "application-services-metering-operator" + "appranix" + "aqua" + "argocd-operator" + ... + ``` + + !!! important + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. + +3. Return list of packages that support `AllNamespaces` install mode and do not use webhooks: + + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/all | jq -cs '[.[] | select(.schema == "olm.bundle" and (.properties[] | select(.type == "olm.csv.metadata").value.installModes[] | select(.type == "AllNamespaces" and .supported == true)) and .spec.webhookdefinitions == null) | .package] | unique[]' + ``` + + ??? success + ``` text title="Example output" + {"package":"ack-acm-controller","version":"0.0.12"} + {"package":"ack-acmpca-controller","version":"0.0.5"} + {"package":"ack-apigatewayv2-controller","version":"1.0.7"} + {"package":"ack-applicationautoscaling-controller","version":"1.0.11"} + {"package":"ack-cloudfront-controller","version":"0.0.9"} + {"package":"ack-cloudtrail-controller","version":"1.0.8"} + {"package":"ack-cloudwatch-controller","version":"0.0.3"} + {"package":"ack-cloudwatchlogs-controller","version":"0.0.4"} + {"package":"ack-dynamodb-controller","version":"1.2.9"} + {"package":"ack-ec2-controller","version":"1.2.4"} + {"package":"ack-ecr-controller","version":"1.0.12"} + {"package":"ack-ecs-controller","version":"0.0.4"} + {"package":"ack-efs-controller","version":"0.0.5"} + {"package":"ack-eks-controller","version":"1.3.3"} + {"package":"ack-elasticache-controller","version":"0.0.29"} + {"package":"ack-emrcontainers-controller","version":"1.0.8"} + {"package":"ack-eventbridge-controller","version":"1.0.6"} + {"package":"ack-iam-controller","version":"1.3.6"} + {"package":"ack-kafka-controller","version":"0.0.4"} + {"package":"ack-keyspaces-controller","version":"0.0.11"} + ... + ``` + +4. Inspect the contents of an extension's metadata: + + ``` terminal + curl -k https://localhost:8443/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select( .schema == "olm.package") | select( .name == "")' + ``` + + `package_name` + : Specifies the name of the package you want to inspect. + + ??? success + ``` text title="Example output" + { + "defaultChannel": "stable-v6.x", + "icon": { + "base64data": "PHN2ZyB4bWxucz0ia... + "mediatype": "image/svg+xml" + }, + "name": "cockroachdb", + "schema": "olm.package" + } + ``` + +### Additional resources + +* [Catalog queries](../howto/catalog-queries.md) diff --git a/docs/tutorials/install-extension.md b/docs/tutorials/install-extension.md new file mode 100644 index 0000000000..b71d5a58c0 --- /dev/null +++ b/docs/tutorials/install-extension.md @@ -0,0 +1,172 @@ +--- +hide: + - toc +--- + +# Install an Extension from a Catalog + +In Operator Lifecycle Manager (OLM) 1.0, Kubernetes extensions are scoped to the cluster. +After you add a catalog to your cluster, you can install an extension by creating a custom resource (CR) and applying it. + +## Prerequisites + +* A catalog that is being served +* The name, and optionally version, or channel, of the [supported extension](../project/olmv1_limitations.md) to be installed +* An existing namespace in which to install the extension + +### ServiceAccount for ClusterExtension Installation and Management + +Adhering to OLM v1's "Secure by Default" tenet, OLM v1 does not have the permissions +necessary to install content. This follows the least privilege principle and reduces +the chance of a [confused deputy attack](https://en.wikipedia.org/wiki/Confused_deputy_problem). +Instead, users must explicitly specify a ServiceAccount that will be used to perform the +installation and management of a specific ClusterExtension. + +The ServiceAccount must be configured with the RBAC permissions required by the ClusterExtension. +If the permissions do not meet the minimum requirements, installation will fail. If no ServiceAccount +is provided in the ClusterExtension manifest, then the manifest will be rejected. + +For information on determining the ServiceAccount's permission, please see [Derive minimal ServiceAccount required for ClusterExtension Installation and Management](../howto/derive-service-account.md). + + +## Procedure + +1. Create a CR for the Kubernetes extension you want to install: + + ``` yaml title="Example CR" + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: + spec: + namespace: + serviceAccount: + name: + source: + sourceType: Catalog + catalog: + packageName: + channels: [," + ``` + + `extension_name` + : Specifies a custom name for the Kubernetes extension you want to install, such as `my-camel-k`. + + `package_name` + : Specifies the name of the package you want to install, such as `camel-k`. + + `channels` + : Optional: Specifies a set of the extension's channels from which to select, such as `stable` or `fast`. + + `version` + : Optional: Specifies the version or version range you want installed, such as `1.3.1` or `"<2"`. + If you use a comparison string to define a version range, the string must be surrounded by double quotes (`"`). + + `namespace_name` + : Specifies a name for the namespace in which the bundle of content for the package referenced + in the packageName field will be applied. + + `serviceAccount_name` + : serviceAccount name is a required reference to a ServiceAccount that exists + in the `namespace_name`. The provided ServiceAccount is used to install and + manage the content for the package specified in the packageName field. + + !!! warning + Currently, the following limitations affect the installation of extensions: + + * If multiple catalogs are added to a cluster, you cannot specify a catalog when you install an extension. + * OLM 1.0 requires that all of the extensions have unique bundle and package names for dependency resolution. + + As a result, if two catalogs have an extension with the same name, the installation might fail or lead to an unintended outcome. + For example, the first extension that matches might install successfully and finish without searching for a match in the second catalog. + +2. Apply the CR to the cluster: + + ``` terminal + kubectl apply -f .yaml + ``` + + !!! success + ``` text title="Example output" + clusterextension.olm.operatorframework.io/argocd created + ``` + +### Verification + +* Describe the installed extension: + + ``` terminal + kubectl describe clusterextensions + ``` + + ??? success + ``` text title="Example output" + Name: argocd + Namespace: + Labels: + Annotations: + API Version: olm.operatorframework.io/v1 + Kind: ClusterExtension + Metadata: + Creation Timestamp: 2024-11-11T13:41:23Z + Finalizers: + olm.operatorframework.io/cleanup-unpack-cache + olm.operatorframework.io/cleanup-contentmanager-cache + Generation: 1 + Resource Version: 5426 + UID: bde55f03-abe2-48af-8c09-28d32df878ad + Spec: + Namespace: argocd + Service Account: + Name: argocd-installer + Source: + Catalog: + Package Name: argocd-operator + Upgrade Constraint Policy: CatalogProvided + Version: 0.6.0 + Source Type: Catalog + Status: + Conditions: + Last Transition Time: 2024-11-11T13:41:23Z + Message: + Observed Generation: 1 + Reason: Deprecated + Status: False + Type: Deprecated + Last Transition Time: 2024-11-11T13:41:23Z + Message: + Observed Generation: 1 + Reason: Deprecated + Status: False + Type: PackageDeprecated + Last Transition Time: 2024-11-11T13:41:23Z + Message: + Observed Generation: 1 + Reason: Deprecated + Status: False + Type: ChannelDeprecated + Last Transition Time: 2024-11-11T13:41:23Z + Message: + Observed Generation: 1 + Reason: Deprecated + Status: False + Type: BundleDeprecated + Last Transition Time: 2024-11-11T13:41:31Z + Message: Installed bundle quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3 successfully + Observed Generation: 1 + Reason: Succeeded + Status: True + Type: Installed + Last Transition Time: 2024-11-11T13:41:32Z + Message: desired state reached + Observed Generation: 1 + Reason: Succeeded + Status: True + Type: Progressing + Install: + Bundle: + Name: argocd-operator.v0.6.0 + Version: 0.6.0 + Events: + ``` diff --git a/docs/Tasks/uninstalling-an-extension.md b/docs/tutorials/uninstall-extension.md similarity index 87% rename from docs/Tasks/uninstalling-an-extension.md rename to docs/tutorials/uninstall-extension.md index 575a7602aa..2345e0edec 100644 --- a/docs/Tasks/uninstalling-an-extension.md +++ b/docs/tutorials/uninstall-extension.md @@ -1,4 +1,9 @@ -# Deleting an extension +--- +hide: + - toc +--- + +# Uninstall an extension You can uninstall a Kubernetes extension and its associated custom resource definitions (CRD) by deleting the extension's custom resource (CR). @@ -18,7 +23,7 @@ You can uninstall a Kubernetes extension and its associated custom resource defi : Specifies the name defined in the `metadata.name` field of the extension's CR. ``` text title="Example output" - clusterextension.olm.operatorframework.io "argocd-operator" deleted + clusterextension.olm.operatorframework.io "argocd" deleted ``` ### Verification @@ -32,7 +37,7 @@ You can uninstall a Kubernetes extension and its associated custom resource defi ``` text title="Example output" No resources found ``` - + ### Cleanup * Remove the extension namespace, and installer service account cluster-scoped RBAC resources (if applicable). diff --git a/docs/tutorials/upgrade-extension.md b/docs/tutorials/upgrade-extension.md new file mode 100644 index 0000000000..b8b2aa5aad --- /dev/null +++ b/docs/tutorials/upgrade-extension.md @@ -0,0 +1,379 @@ +--- +hide: + - toc +--- + +# Upgrade an Extension + +Existing extensions can be upgraded by updating the version field in the ClusterExtension resource. + +For information on downgrading an extension, see [Downgrade an Extension](downgrade-extension.md). + +## Prerequisites + +* You have a ClusterExtension installed +* The target version is compatible with OLM v1 (see [OLM v1 limitations](../project/olmv1_limitations.md)) +* Any changes to the CustomResourceDefinition in the new version meet compatibility requirements (see [CRD upgrade safety](../concepts/crd-upgrade-safety.md)) +* The installer ServiceAccount's RBAC permissions are adequate for the target version (see [Minimal RBAC for Installer Service Account](../howto/derive-service-account.md)) +* You are not attempting to upgrade between minor versions with a major version of zero (see [Upgrades within the major version zero](../concepts/upgrade-support.md#upgrades-within-the-major-version-zero)) + +For more detailed information see [Upgrade Support](../concepts/upgrade-support.md). + +## Procedure + +For this example, we will be using v0.2.0 of the ArgoCD operator. If you would like to follow along +with this tutorial, you can apply the following manifest to your cluster by, for example, +saving it to a local file and then running `kubectl apply -f FILENAME`: + +??? info "ArgoCD v0.2.0 manifests" + ```yaml + --- + apiVersion: v1 + kind: Namespace + metadata: + name: argocd + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: argocd-installer + namespace: argocd + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: argocd-installer-binding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-installer-clusterrole + subjects: + - kind: ServiceAccount + name: argocd-installer + namespace: argocd + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: argocd-installer-clusterrole + rules: + # Allow ClusterExtension to set blockOwnerDeletion ownerReferences + - apiGroups: [olm.operatorframework.io] + resources: [clusterextensions/finalizers] + verbs: [update] + resourceNames: [argocd] + # Manage ArgoCD CRDs + - apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [create, list, watch] + - apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [get, update, patch, delete] + resourceNames: + - appprojects.argoproj.io + - argocds.argoproj.io + - applications.argoproj.io + - argocdexports.argoproj.io + - applicationsets.argoproj.io + # Manage ArgoCD ClusterRoles and ClusterRoleBindings + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [create, list, watch] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [get, update, patch, delete] + resourceNames: + - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx + - argocd-operator-metrics-reader + - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: [create, list, watch] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: [get, update, patch, delete] + resourceNames: + - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx + - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: argocd-installer-rbac-binding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-installer-rbac-clusterrole + subjects: + - kind: ServiceAccount + name: argocd-installer + namespace: argocd + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: argocd-installer-rbac-clusterrole + rules: + - apiGroups: [""] + resources: [configmaps] + verbs: ['*'] + - apiGroups: [""] + resources: [endpoints] + verbs: ['*'] + - apiGroups: [""] + resources: [events] + verbs: ['*'] + - apiGroups: [""] + resources: [namespaces] + verbs: ['*'] + - apiGroups: [""] + resources: [persistentvolumeclaims] + verbs: ['*'] + - apiGroups: [""] + resources: [pods] + verbs: ['*', get] + - apiGroups: [""] + resources: [pods/log] + verbs: [get] + - apiGroups: [""] + resources: [secrets] + verbs: ['*'] + - apiGroups: [""] + resources: [serviceaccounts] + verbs: ['*'] + - apiGroups: [""] + resources: [services] + verbs: ['*'] + - apiGroups: [""] + resources: [services/finalizers] + verbs: ['*'] + - apiGroups: [apps] + resources: [daemonsets] + verbs: ['*'] + - apiGroups: [apps] + resources: [deployments] + verbs: ['*'] + - apiGroups: [apps] + resources: [deployments/finalizers] + resourceNames: [argocd-operator] + verbs: [update] + - apiGroups: [apps] + resources: [replicasets] + verbs: ['*'] + - apiGroups: [apps] + resources: [statefulsets] + verbs: ['*'] + - apiGroups: [apps.openshift.io] + resources: [deploymentconfigs] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [applications] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [appprojects] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocdexports] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocdexports/finalizers] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocdexports/status] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocds] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocds/finalizers] + verbs: ['*'] + - apiGroups: [argoproj.io] + resources: [argocds/status] + verbs: ['*'] + - apiGroups: [authentication.k8s.io] + resources: [tokenreviews] + verbs: [create] + - apiGroups: [authorization.k8s.io] + resources: [subjectaccessreviews] + verbs: [create] + - apiGroups: [autoscaling] + resources: [horizontalpodautoscalers] + verbs: ['*'] + - apiGroups: [batch] + resources: [cronjobs] + verbs: ['*'] + - apiGroups: [batch] + resources: [jobs] + verbs: ['*'] + - apiGroups: [config.openshift.io] + resources: [clusterversions] + verbs: [get, list, watch] + - apiGroups: [monitoring.coreos.com] + resources: [prometheuses] + verbs: ['*'] + - apiGroups: [monitoring.coreos.com] + resources: [servicemonitors] + verbs: ['*'] + - apiGroups: [networking.k8s.io] + resources: [ingresses] + verbs: ['*'] + - apiGroups: [rbac.authorization.k8s.io] + resources: ['*'] + verbs: ['*'] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: ['*'] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: ['*'] + - apiGroups: [route.openshift.io] + resources: [routes] + verbs: ['*'] + - apiGroups: [route.openshift.io] + resources: [routes/custom-host] + verbs: ['*'] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: argocd-installer-role + namespace: argocd + rules: + - apiGroups: [""] + resources: [serviceaccounts] + verbs: [create, list, watch] + - apiGroups: [""] + resources: [serviceaccounts] + verbs: [get, update, patch, delete] + resourceNames: [argocd-operator-controller-manager] + - apiGroups: [""] + resources: [configmaps] + verbs: [create, list, watch] + - apiGroups: [""] + resources: [configmaps] + verbs: [get, update, patch, delete] + resourceNames: [argocd-operator-manager-config] + - apiGroups: [""] + resources: [services] + verbs: [create, list, watch] + - apiGroups: [""] + resources: [services] + verbs: [get, update, patch, delete] + resourceNames: [argocd-operator-controller-manager-metrics-service] + - apiGroups: [apps] + resources: [deployments] + verbs: [create, list, watch] + - apiGroups: [apps] + resources: [deployments] + verbs: [get, update, patch, delete] + resourceNames: [argocd-operator-controller-manager] + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: argocd-installer-binding + namespace: argocd + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-installer-role + subjects: + - kind: ServiceAccount + name: argocd-installer + namespace: argocd + --- + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: argocd + spec: + namespace: argocd + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.2.0 + ``` + +If we view the current state of our ClusterExtension we should see that we have installed version 0.2.0: + +```terminal +kubectl get clusterextension argocd -o jsonpath-as-json="{.status.install}" +``` + +!!! success "Command output" + ``` json + [ + { + "bundle": { + "name": "argocd-operator.v0.2.0", + "version": "0.2.0" + } + } + ] + ``` + +* To initiate our upgrade, let's update the version field in the ClusterExtension resource: + + ``` terminal title="Method 1: apply a new ClusterExtension manifest" + kubectl apply -f - < k8s.io/api v0.34.0 + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.34.0 + +replace k8s.io/apimachinery => k8s.io/apimachinery v0.34.0 + +replace k8s.io/apiserver => k8s.io/apiserver v0.34.0 + +replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.34.0 + +replace k8s.io/client-go => k8s.io/client-go v0.34.0 + +replace k8s.io/cloud-provider => k8s.io/cloud-provider v0.34.0 + +replace k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.34.0 + +replace k8s.io/code-generator => k8s.io/code-generator v0.34.0 + +replace k8s.io/component-base => k8s.io/component-base v0.34.0 + +replace k8s.io/component-helpers => k8s.io/component-helpers v0.34.0 + +replace k8s.io/controller-manager => k8s.io/controller-manager v0.34.0 + +replace k8s.io/cri-api => k8s.io/cri-api v0.34.0 + +replace k8s.io/cri-client => k8s.io/cri-client v0.34.0 + +replace k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.34.0 + +replace k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.34.0 + +replace k8s.io/endpointslice => k8s.io/endpointslice v0.34.0 + +replace k8s.io/externaljwt => k8s.io/externaljwt v0.34.0 + +replace k8s.io/kms => k8s.io/kms v0.34.0 + +replace k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.34.0 + +replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.34.0 + +replace k8s.io/kube-proxy => k8s.io/kube-proxy v0.34.0 + +replace k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.34.0 + +replace k8s.io/kubectl => k8s.io/kubectl v0.34.0 + +replace k8s.io/kubelet => k8s.io/kubelet v0.34.0 + +replace k8s.io/kubernetes => k8s.io/kubernetes v1.34.0 + +replace k8s.io/metrics => k8s.io/metrics v0.34.0 + +replace k8s.io/mount-utils => k8s.io/mount-utils v0.34.0 + +replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.34.0 + +replace k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.34.0 diff --git a/go.sum b/go.sum index ae892a5162..6c1ceb1a85 100644 --- a/go.sum +++ b/go.sum @@ -1,255 +1,221 @@ -carvel.dev/kapp v0.63.3 h1:YKFQa8INOsmdlflHWJGhB4oSvWbOxU0Niybok1/YPaI= -carvel.dev/kapp v0.63.3/go.mod h1:JJfWYClyhCed6rhwuRqjAbBGvDl0/EGMf1MQ/FKYbWw= -carvel.dev/vendir v0.40.0 h1:JdhCp/EjAPGI8F5zoAVYwZHf1sPEFee19RpgGb3ciT8= -carvel.dev/vendir v0.40.0/go.mod h1:XPdluJu7322RZNx05AA4gYnV52aKywBdh7Ma12GuM2Q= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= 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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= -github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-lambda-go v1.26.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cert-manager/cert-manager v1.18.2 h1:H2P75ycGcTMauV3gvpkDqLdS3RSXonWF2S49QGA1PZE= +github.com/cert-manager/cert-manager v1.18.2/go.mod h1:icDJx4kG9BCNpGjBvrmsFd99d+lXUvWdkkcrSSQdIiw= 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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= -github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0= -github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= -github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= -github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= +github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= +github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= +github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= -github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/containers/common v0.60.2 h1:utcwp2YkO8c0mNlwRxsxfOiqfj157FRrBjxgjR6f+7o= -github.com/containers/common v0.60.2/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= -github.com/containers/image/v5 v5.32.2 h1:SzNE2Y6sf9b1GJoC8qjCuMBXwQrACFp4p0RK15+4gmQ= -github.com/containers/image/v5 v5.32.2/go.mod h1:v1l73VeMugfj/QtKI+jhYbwnwFCFnNGckvbST3rQ5Hk= +github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE= +github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM= -github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U= -github.com/containers/storage v1.55.0 h1:wTWZ3YpcQf1F+dSP4KxG9iqDfpQY1otaUXjPpffuhgg= -github.com/containers/storage v1.55.0/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbFOeLunhER1RQ= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cppforlife/cobrautil v0.0.0-20200514214827-bb86e6965d72/go.mod h1:2w+qxVu2KSGW78Ex/XaIqfh/OvBgjEsmN53S4T8vEyA= -github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef h1:de10GNLe45JTMghl2qf9WH17H/BjGShK41X3vKAsPJA= -github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef/go.mod h1:2w+qxVu2KSGW78Ex/XaIqfh/OvBgjEsmN53S4T8vEyA= -github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 h1:mYQweUIBD+TBRjIeQnJmXr0GSVMpI6O0takyb/aaOgo= -github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835/go.mod h1:dYeVsKp1vvK8XjdTPR1gF+uk+9doxKeO3hqQTOCr7T4= -github.com/cppforlife/go-cli-ui v0.0.0-20200505234325-512793797f05/go.mod h1:I0qrzCmuPWYI6kAOvkllYjaW2aovclWbJ96+v+YyHb0= -github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14 h1:MjRdR01xh0sfkeS3OOBv+MYkYsrbHuTDc4rfBnVdFaI= -github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14/go.mod h1:AlgTssDlstr4mf92TR4DPITLfl5+7wEY4cKStCmeeto= -github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b h1:+8LQctLhaj+63L/37l8IK/5Q3odN6RzWlglonUwrKok= -github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE= -github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfGUhc8I+MPfRis8dZ818Ic= -github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.2.0+incompatible h1:yHD1QEB1/0vr5eBNpu8tncu8gWxg8EydFPOSKHzXSMM= -github.com/docker/cli v27.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY= +github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI= +github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk= +github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= -github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/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 v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.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/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= -github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= +github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -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-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -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-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= +github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= +github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= +github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= +github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= +github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= +github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= +github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= +github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= +github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= +github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= +github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= +github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= +github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= +github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= +github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= +github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= +github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= +github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= +github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= +github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= +github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= +github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= +github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= +github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= +github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 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/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= +github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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.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= @@ -257,155 +223,89 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU 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.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/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= +github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= -github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= +github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= 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/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-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -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/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -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 v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= -github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= -github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= -github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hpcloud/tail v1.0.1-0.20180514194441-a1dbeea552b7/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ= +github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joelanford/ignore v0.1.0 h1:VawbTDeg5EL+PN7W8gxVzGerfGpVo3gFdR5ZAqnkYRk= -github.com/joelanford/ignore v0.1.0/go.mod h1:Vb0PQMAQXK29fmiPjDukpO8I2NTcp1y8LbhFijD1/0o= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= +github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joelanford/ignore v0.1.1 h1:vKky5RDoPT+WbONrbQBgOn95VV/UPh4ejlyAbbzgnQk= +github.com/joelanford/ignore v0.1.1/go.mod h1:8eho/D8fwQ3rIXrLwE23AaeaGDNXqLE9QJ3zJ4LIPCw= 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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4= -github.com/k14s/difflib v0.0.0-20240118055029-596a7a5585c3 h1:q2ikACDbDDbyUcN9JkDcNMGhIx1EBRkctAsPZMr35qM= -github.com/k14s/difflib v0.0.0-20240118055029-596a7a5585c3/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4= -github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk= -github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA= -github.com/k14s/ytt v0.36.0 h1:ERr7q+r3ziYJv91fvTx2b76d1MIo3SI/EsAS01WU+Zo= -github.com/k14s/ytt v0.36.0/go.mod h1:awQ3bHBk1qT2Xn3GJVdmaLss2khZOIBBKFd2TNXZNMk= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -414,415 +314,325 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/letsencrypt/boulder v0.0.0-20250624003606-5ddd5acf990d h1:fCRb9hXR4QQJpwc7xnGugnva0DD5ollTGkys0n8aXT4= +github.com/letsencrypt/boulder v0.0.0-20250624003606-5ddd5acf990d/go.mod h1:BVoSL2Ed8oCncct0meeBqoTY7b1Mzx7WqEOZ8EisFmY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= -github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= -github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= -github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 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 h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 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/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= -github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openshift/crd-schema-checker v0.0.0-20240404194209-35a9033b1d11 h1:eTNDkNRNV5lZvUbVM9Nop0lBcljSnA8rZX6yQPZ0ZnU= -github.com/openshift/crd-schema-checker v0.0.0-20240404194209-35a9033b1d11/go.mod h1:EmVJt97N+pfWFsli/ipXTBZqSG5F5KGQhm3c3IsGq1o= -github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/6VpifiXI= -github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM= -github.com/operator-framework/catalogd v0.26.0 h1:RDzNEv631o7WgkXGfFrOCiFBaBMwK621/vinRuOS2LI= -github.com/operator-framework/catalogd v0.26.0/go.mod h1:pR03BacyPJPeVk6KM5OW6CLOoqkHzvyncQSZmiO3+IQ= -github.com/operator-framework/helm-operator-plugins v0.5.0 h1:qph2OoECcI9mpuUBtOsWOMgvpx52mPTTSvzVxICsT04= -github.com/operator-framework/helm-operator-plugins v0.5.0/go.mod h1:yVncrZ/FJNqedMil+055fk6sw8aMKRrget/AqGM0ig0= -github.com/operator-framework/operator-lib v0.15.0 h1:0QeRM4PMtThqINpcFGCEBnIV3Z8u7/8fYLEx6mUtdcM= -github.com/operator-framework/operator-lib v0.15.0/go.mod h1:ZxLvFuQ7bRWiTNBOqodbuNvcsy/Iq0kOygdxhlbNdI0= -github.com/operator-framework/operator-registry v1.47.0 h1:Imr7X/W6FmXczwpIOXfnX8d6Snr1dzwWxkMG+lLAfhg= -github.com/operator-framework/operator-registry v1.47.0/go.mod h1:CJ3KcP8uRxtC8l9caM1RsV7r7jYlKAd452tcxcgXyTQ= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= -github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/operator-framework/api v0.36.0 h1:6+duRhamCvB540JbvNp/1+Pot7luff7HqdAOm9bAntg= +github.com/operator-framework/api v0.36.0/go.mod h1:QSmHMx8XpGsNWvjU5CUelVZC916VLp/TZhfYvGKpghM= +github.com/operator-framework/helm-operator-plugins v0.8.0 h1:0f6HOQC5likkf0b/OvGvw7nhDb6h8Cj5twdCNjwNzMc= +github.com/operator-framework/helm-operator-plugins v0.8.0/go.mod h1:Sc+8bE38xTCgCChBUvtq/PxatEg9fAypr7S5iAw8nlA= +github.com/operator-framework/operator-lib v0.17.0 h1:cbz51wZ9+GpWR1ZYP4CSKSSBxDlWxmmnseaHVZZjZt4= +github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo26R3NFp1eNlqhQNmzqhOblaLw= +github.com/operator-framework/operator-registry v1.60.0 h1:eUP14WThVTNx+/5hQR9Jyg0nxbf5cOg7hK/GgaOA5Tg= +github.com/operator-framework/operator-registry v1.60.0/go.mod h1:PojPivJbKZgD9RG77JWxFpQRo3iCoUn6WR3aTiS6HBI= +github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -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/proglottis/gpgme v0.1.5 h1:KCGyOw8sQ+SI96j6G8D8YkOGn+1TwbQTT9/zQXoVlz0= +github.com/proglottis/gpgme v0.1.5/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/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.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= +github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/redis/go-redis/extra/rediscmd/v9 v9.10.0 h1:uTiEyEyfLhkw678n6EulHVto8AkcXVr8zUcBJNZ0ark= +github.com/redis/go-redis/extra/rediscmd/v9 v9.10.0/go.mod h1:eFYL/99JvdLP4T9/3FZ5t2pClnv7mMskc+WstTcyVr4= +github.com/redis/go-redis/extra/redisotel/v9 v9.10.0 h1:4z7/hCJ9Jft8EBb2tDmK38p2WjyIEJ1ShhhwAhjOCps= +github.com/redis/go-redis/extra/redisotel/v9 v9.10.0/go.mod h1:B0thqLh4hB8MvvcUKSwyP5YiIcCCp8UrQ0cA9gEqyjk= +github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs= +github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -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/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= -github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= +github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sigstore/fulcio v1.7.1 h1:RcoW20Nz49IGeZyu3y9QYhyyV3ZKQ85T+FXPKkvE+aQ= +github.com/sigstore/fulcio v1.7.1/go.mod h1:7lYY+hsd8Dt+IvKQRC+KEhWpCZ/GlmNvwIa5JhypMS8= +github.com/sigstore/protobuf-specs v0.4.3 h1:kRgJ+ciznipH9xhrkAbAEHuuxD3GhYnGC873gZpjJT4= +github.com/sigstore/protobuf-specs v0.4.3/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU= +github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -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/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA= +github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= -github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec/go.mod h1:wPlfmglZmRWMYv/qJy3P+fK/UnoQB5ISk4txfNd9tDo= -github.com/vito/go-interact v1.0.1 h1:O8xi8c93bRUv2Tb/v6HdiuGc+WnWt+AQzF74MOOdlBs= -github.com/vito/go-interact v1.0.1/go.mod h1:HrdHSJXD2yn1MhlTwSIMeFgQ5WftiIorszVGd3S/DAA= -github.com/vmware-tanzu/carvel-kapp-controller v0.51.0 h1:lCCHy9n/AzWPtq5gqbINJHgmF32RCUkh9DbVQgx6HAs= -github.com/vmware-tanzu/carvel-kapp-controller v0.51.0/go.mod h1:go1MQz1D2kVgjaE2ZHtuHGECFk8EDLeXMpjmDNDzuJM= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= +github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= +github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= -go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= -go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= -go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= -go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= -go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= -go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= -go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= -go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.podman.io/common v0.65.0 h1:8JNl25U4VpKDkFHSymSPm4te7ZQHJbfAB/l2FqtmYEg= +go.podman.io/common v0.65.0/go.mod h1:+lJu8KHeoDQsD9HDdiFaMaOUiqPLQnK406WuLnqM7Z0= +go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4= +go.podman.io/image/v5 v5.38.0/go.mod h1:hSIoIUzgBnmc4DjoIdzk63aloqVbD7QXDMkSE/cvG90= +go.podman.io/storage v1.61.0 h1:5hD/oyRYt1f1gxgvect+8syZBQhGhV28dCw2+CZpx0Q= +go.podman.io/storage v1.61.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 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-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -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/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180730214132-a0f8a16cb08c/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 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.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190222072716-a9d3bda3a223/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-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-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -831,93 +641,83 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -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.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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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-20190328211700-ab21143f2384/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-20191112195655-aa38f8e97acc/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= +golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= 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= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 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/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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -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-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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= @@ -927,88 +727,81 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 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.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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20140529071818-c131134a1947/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -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.4/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.0-20200615113413-eeeca48fe776/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 v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.15.4 h1:UFHd6oZ1IN3FsUZ7XNhOQDyQ2QYknBNWRHH57e9cbHY= -helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= 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= -k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= -k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= -k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= -k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= -k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= -k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= -k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= -k8s.io/cli-runtime v0.31.0 h1:V2Q1gj1u3/WfhD475HBQrIYsoryg/LrhhK4RwpN+DhA= -k8s.io/cli-runtime v0.31.0/go.mod h1:vg3H94wsubuvWfSmStDbekvbla5vFGC+zLWqcf+bGDw= -k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= -k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= -k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= -k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= +k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= +k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw= +k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= +k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= +k8s.io/component-helpers v0.34.0 h1:5T7P9XGMoUy1JDNKzHf0p/upYbeUf8ZaSf9jbx0QlIo= +k8s.io/component-helpers v0.34.0/go.mod h1:kaOyl5tdtnymriYcVZg4uwDBe2d1wlIpXyDkt6sVnt4= +k8s.io/controller-manager v0.34.0 h1:oCHoqS8dcFp7zDSu7HUvTpakq3isSxil3GprGGlJMsE= +k8s.io/controller-manager v0.34.0/go.mod h1:XFto21U+Mm9BT8r/Jd5E4tHCGtwjKAUFOuDcqaj2VK0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.31.0 h1:kANwAAPVY02r4U4jARP/C+Q1sssCcN/1p9Nk+7BQKVg= -k8s.io/kubectl v0.31.0/go.mod h1:pB47hhFypGsaHAPjlwrNbvhXgmuAr01ZBvAIIUaI8d4= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= -oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= -sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -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/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= -sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= -sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= -sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= -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= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= +k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= +k8s.io/kubernetes v1.34.0 h1:NvUrwPAVB4W3mSOpJ/RtNGHWWYyUP/xPaX5rUSpzA0w= +k8s.io/kubernetes v1.34.0/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +pkg.package-operator.run/boxcutter v0.7.1 h1:us5wn0px9aAkumrXiQx38+Sc9dTgKJsHFbePoRQeWRo= +pkg.package-operator.run/boxcutter v0.7.1/go.mod h1:xEOKM3e3xtfSKYIOssQnu6DOWceiIu52qziMDcmUmpI= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= +sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= +sigs.k8s.io/crdify v0.5.0 h1:mrMH9CgXQPTZUpTU6Klqfnlys8bggv/7uvLT2lXSP7A= +sigs.k8s.io/crdify v0.5.0/go.mod h1:ZIFxaYNgKYmFtZCLPysncXQ8oqwnNlHQbRUfxJHZwzU= +sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= +sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/hack/OWNERS b/hack/OWNERS new file mode 100644 index 0000000000..835cabe507 --- /dev/null +++ b/hack/OWNERS @@ -0,0 +1,2 @@ +approvers: + - ci-approvers diff --git a/hack/ci/custom-linters/analyzers/analyzers_test.go b/hack/ci/custom-linters/analyzers/analyzers_test.go new file mode 100644 index 0000000000..053ffc3055 --- /dev/null +++ b/hack/ci/custom-linters/analyzers/analyzers_test.go @@ -0,0 +1,12 @@ +package analyzers + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestSetupLogErrorCheck(t *testing.T) { + testdata := analysistest.TestData() + analysistest.Run(t, testdata, SetupLogErrorCheck) +} diff --git a/hack/ci/custom-linters/analyzers/setuplognilerrorcheck.go b/hack/ci/custom-linters/analyzers/setuplognilerrorcheck.go new file mode 100644 index 0000000000..ba9098a5f5 --- /dev/null +++ b/hack/ci/custom-linters/analyzers/setuplognilerrorcheck.go @@ -0,0 +1,120 @@ +package analyzers + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" +) + +var SetupLogErrorCheck = &analysis.Analyzer{ + Name: "setuplogerrorcheck", + Doc: "Detects and reports improper usages of logger.Error() calls to enforce good practices " + + "and prevent silent failures.", + Run: runSetupLogErrorCheck, +} + +func runSetupLogErrorCheck(pass *analysis.Pass) (interface{}, error) { + for _, f := range pass.Files { + ast.Inspect(f, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // Ensure function being called is logger.Error + selectorExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok || selectorExpr.Sel.Name != "Error" { + return true + } + + // Ensure receiver (logger) is identified + ident, ok := selectorExpr.X.(*ast.Ident) + if !ok { + return true + } + + // Verify if the receiver is logr.Logger + obj := pass.TypesInfo.ObjectOf(ident) + if obj == nil { + return true + } + + named, ok := obj.Type().(*types.Named) + if !ok || named.Obj().Pkg() == nil || named.Obj().Pkg().Path() != "github.com/go-logr/logr" || named.Obj().Name() != "Logger" { + return true + } + + if len(callExpr.Args) == 0 { + return true + } + + // Get the actual source code line where the issue occurs + var srcBuffer bytes.Buffer + if err := format.Node(&srcBuffer, pass.Fset, callExpr); err != nil { + return true + } + sourceLine := srcBuffer.String() + + // Check if the first argument of the error log is nil + firstArg, ok := callExpr.Args[0].(*ast.Ident) + if ok && firstArg.Name == "nil" { + suggestedError := "errors.New(\"kind error (i.e. configuration error)\")" + suggestedMessage := "\"error message describing the failed operation\"" + + if len(callExpr.Args) > 1 { + if msgArg, ok := callExpr.Args[1].(*ast.BasicLit); ok && msgArg.Kind == token.STRING { + suggestedMessage = msgArg.Value + } + } + + pass.Reportf(callExpr.Pos(), + "Incorrect usage of 'logger.Error(nil, ...)'. The first argument must be a non-nil 'error'. "+ + "Passing 'nil' may hide error details, making debugging harder.\n\n"+ + "\U0001F41B **What is wrong?**\n %s\n\n"+ + "\U0001F4A1 **How to solve? Return the error, i.e.:**\n logger.Error(%s, %s, \"key\", value)\n\n", + sourceLine, suggestedError, suggestedMessage) + return true + } + + // Ensure at least two arguments exist (error + message) + if len(callExpr.Args) < 2 { + pass.Reportf(callExpr.Pos(), + "Incorrect usage of 'logger.Error(error, ...)'. Expected at least an error and a message string.\n\n"+ + "\U0001F41B **What is wrong?**\n %s\n\n"+ + "\U0001F4A1 **How to solve?**\n Provide a message, e.g. logger.Error(err, \"descriptive message\")\n\n", + sourceLine) + return true + } + + // Ensure key-value pairs (if any) are valid + if (len(callExpr.Args)-2)%2 != 0 { + pass.Reportf(callExpr.Pos(), + "Incorrect usage of 'logger.Error(error, \"msg\", ...)'. Key-value pairs must be provided after the message, but an odd number of arguments was found.\n\n"+ + "\U0001F41B **What is wrong?**\n %s\n\n"+ + "\U0001F4A1 **How to solve?**\n Ensure all key-value pairs are complete, e.g. logger.Error(err, \"msg\", \"key\", value, \"key2\", value2)\n\n", + sourceLine) + return true + } + + for i := 2; i < len(callExpr.Args); i += 2 { + keyArg := callExpr.Args[i] + keyType := pass.TypesInfo.TypeOf(keyArg) + if keyType == nil || keyType.String() != "string" { + pass.Reportf(callExpr.Pos(), + "Incorrect usage of 'logger.Error(error, \"msg\", key, value)'. Keys in key-value pairs must be strings, but got: %s.\n\n"+ + "\U0001F41B **What is wrong?**\n %s\n\n"+ + "\U0001F4A1 **How to solve?**\n Ensure keys are strings, e.g. logger.Error(err, \"msg\", \"key\", value)\n\n", + keyType, sourceLine) + return true + } + } + + return true + }) + } + return nil, nil +} diff --git a/hack/ci/custom-linters/analyzers/testdata/go.mod b/hack/ci/custom-linters/analyzers/testdata/go.mod new file mode 100644 index 0000000000..23875e2335 --- /dev/null +++ b/hack/ci/custom-linters/analyzers/testdata/go.mod @@ -0,0 +1,5 @@ +module testdata + +go 1.24.3 + +require github.com/go-logr/logr v1.4.3 diff --git a/hack/ci/custom-linters/analyzers/testdata/go.sum b/hack/ci/custom-linters/analyzers/testdata/go.sum new file mode 100644 index 0000000000..7ef38a47f2 --- /dev/null +++ b/hack/ci/custom-linters/analyzers/testdata/go.sum @@ -0,0 +1,4 @@ +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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/hack/ci/custom-linters/analyzers/testdata/main.go b/hack/ci/custom-linters/analyzers/testdata/main.go new file mode 100644 index 0000000000..97e712f506 --- /dev/null +++ b/hack/ci/custom-linters/analyzers/testdata/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/go-logr/logr" +) + +func testLogger() { + var logger logr.Logger + var err error + var value int + + // Case 1: Nil error - Ensures the first argument cannot be nil. + logger.Error(nil, "message") // want ".*may hide error details, making debugging harder*" + + // Case 2: Odd number of key-value arguments - Ensures key-value pairs are complete. + logger.Error(err, "message", "key1") // want ".*Key-value pairs must be provided after the message, but an odd number of arguments was found.*" + + // Case 3: Key in key-value pair is not a string - Ensures keys in key-value pairs are strings. + logger.Error(err, "message", 123, value) // want ".*Ensure keys are strings.*" + + // Case 4: Values are passed without corresponding keys - Ensures key-value arguments are structured properly. + logger.Error(err, "message", value, "key2", value) // want ".*Key-value pairs must be provided after the message, but an odd number of arguments was found.*" + + // Case 5: Correct Usage - Should not trigger any warnings. + logger.Error(err, "message", "key1", value, "key2", "value") +} diff --git a/hack/ci/custom-linters/cmd/main.go b/hack/ci/custom-linters/cmd/main.go new file mode 100644 index 0000000000..9370ad90d8 --- /dev/null +++ b/hack/ci/custom-linters/cmd/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/unitchecker" + + "github.com/operator-framework/operator-controller/hack/ci/custom-linters/analyzers" +) + +// Define the custom Linters implemented in the project +var customLinters = []*analysis.Analyzer{ + analyzers.SetupLogErrorCheck, +} + +func main() { + unitchecker.Main(customLinters...) +} diff --git a/hack/demo/catalogd-demo-script.sh b/hack/demo/catalogd-demo-script.sh new file mode 100755 index 0000000000..e7f226f24f --- /dev/null +++ b/hack/demo/catalogd-demo-script.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# +# Welcome to the catalogd demo +# +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + + +kind delete cluster +kind create cluster +kubectl cluster-info --context kind-kind +sleep 10 + +# use the install script from the latest github release +curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash + +# inspect crds (clustercatalog) +kubectl get crds -A +kubectl get clustercatalog -A + +echo "... checking catalogd controller is available" +kubectl wait --for=condition=Available -n olmv1-system deploy/catalogd-controller-manager --timeout=1m +echo "... checking clustercatalog is serving" +kubectl wait --for=condition=Serving clustercatalog/operatorhubio --timeout=60s +echo "... checking clustercatalog is finished unpacking" +kubectl wait --for=condition=Progressing=True clustercatalog/operatorhubio --timeout=60s + +# port forward the catalogd-service service to interact with the HTTP server serving catalog contents +(kubectl -n olmv1-system port-forward svc/catalogd-service 8081:443)& + +sleep 3 + +# check what 'packages' are available in this catalog +curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.package") | .name' +# check what channels are included in the wavefront package +curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.channel") | select(.package == "wavefront") | .name' +# check what bundles are included in the wavefront package +curl -k https://localhost:8081/catalogs/operatorhubio/api/v1/all | jq -s '.[] | select(.schema == "olm.bundle") | select(.package == "wavefront") | .name' diff --git a/hack/demo/catalogd-metas-demo-script.sh b/hack/demo/catalogd-metas-demo-script.sh new file mode 100755 index 0000000000..63fb84b838 --- /dev/null +++ b/hack/demo/catalogd-metas-demo-script.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# Welcome to the catalogd metas API endpoint demo +# +trap 'trap - SIGTERM && kill -- -"$$"' SIGINT SIGTERM EXIT + +kind delete cluster +kind create cluster +kubectl cluster-info --context kind-kind +sleep 10 + +# use the install script from the latest github release +curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash + +# inspect crds (clustercatalog) +kubectl get crds -A +kubectl get clustercatalog -A + +# ... checking catalogd controller is available +kubectl wait --for=condition=Available -n olmv1-system deploy/catalogd-controller-manager --timeout=1m + +# patch the deployment to include the feature gate +kubectl patch -n olmv1-system deploy/catalogd-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=APIV1MetasHandler=true"}]' + +# ... waiting for new deployment for catalogd controller to become available +kubectl rollout status -n olmv1-system deploy/catalogd-controller-manager +kubectl wait --for=condition=Available -n olmv1-system deploy/catalogd-controller-manager --timeout=1m +# ... checking clustercatalog is serving +kubectl wait --for=condition=Serving clustercatalog/operatorhubio --timeout=60s +# ... checking clustercatalog is finished unpacking (progressing gone back to true) +kubectl wait --for=condition=Progressing=True clustercatalog/operatorhubio --timeout=60s + + +# port forward the catalogd-service service to interact with the HTTP server serving catalog contents +(kubectl -n olmv1-system port-forward svc/catalogd-service 8081:443)& + + +# check what 'packages' are available in this catalog +curl -f --retry-all-errors --retry 10 -k 'https://localhost:8081/catalogs/operatorhubio/api/v1/metas?schema=olm.package' | jq -s '.[] | .name' +# check what channels are included in the wavefront package +curl -f -k 'https://localhost:8081/catalogs/operatorhubio/api/v1/metas?schema=olm.channel&package=wavefront' | jq -s '.[] |.name' +# check what bundles are included in the wavefront package +curl -f -k 'https://localhost:8081/catalogs/operatorhubio/api/v1/metas?schema=olm.bundle&package=wavefront' | jq -s '.[] |.name' + diff --git a/hack/demo/generate-asciidemo.sh b/hack/demo/generate-asciidemo.sh new file mode 100755 index 0000000000..737c230884 --- /dev/null +++ b/hack/demo/generate-asciidemo.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +trap cleanup SIGINT SIGTERM EXIT + +SCRIPTPATH="$( cd -- "$(dirname "$0")" > /dev/null 2>&1 ; pwd -P )" +export DEMO_RESOURCE_DIR="${SCRIPTPATH}/resources" + +check_prereq() { + prog=$1 + if ! command -v ${prog} &> /dev/null + then + echo "unable to find prerequisite: $1" + exit 1 + fi +} + +cleanup() { + if [[ -n "${WKDIR-}" && -d $WKDIR ]]; then + rm -rf $WKDIR + fi +} + +usage() { + echo "$0 [options] " + echo "" + echo "options:" + echo " -n " + echo " -u upload cast (default: false)" + echo " -h help (this message)" + echo "" + echo "examples:" + echo " # Generate asciinema demo described by gzip-demo-script.sh into gzip-demo-script.cast" + echo " $0 gzip-demo-script.sh" + echo "" + echo " # Generate asciinema demo described by demo-script.sh into catalogd-demo.cast" + echo " $0 -n catalogd-demo demo-script.sh" + echo "" + echo " # Generate and upload catalogd-demo.cast" + echo " $0 -u -n catalogd-demo demo-script.sh" + exit 1 +} + +set +u +while getopts ':hn:u' flag; do + case "${flag}" in + h) + usage + ;; + n) + DEMO_NAME="${OPTARG}" + ;; + u) + UPLOAD=true + ;; + :) + echo "Error: Option -${OPTARG} requires an argument." + usage + ;; + \?) + echo "Error: Invalid option -${OPTARG}" + usage + ;; + esac +done +shift $((OPTIND - 1)) +set -u + +DEMO_SCRIPT="${1-}" + +if [ -z $DEMO_SCRIPT ]; then + usage +fi + +WKDIR=$(mktemp -d -t generate-asciidemo.XXXXX) +if [ ! -d ${WKDIR} ]; then + echo "unable to create temporary workspace" + exit 2 +fi + +for prereq in "asciinema curl"; do + check_prereq ${prereq} +done + +curl https://raw.githubusercontent.com/zechris/asciinema-rec_script/main/bin/asciinema-rec_script -o ${WKDIR}/asciinema-rec_script +chmod +x ${WKDIR}/asciinema-rec_script +screencast=${WKDIR}/${DEMO_NAME}.cast ${WKDIR}/asciinema-rec_script ${SCRIPTPATH}/${DEMO_SCRIPT} + +if [ -n "${UPLOAD-}" ]; then + asciinema upload ${WKDIR}/${DEMO_NAME}.cast +fi + diff --git a/hack/demo/gzip-demo-script.sh b/hack/demo/gzip-demo-script.sh new file mode 100755 index 0000000000..2cd1bb7946 --- /dev/null +++ b/hack/demo/gzip-demo-script.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT +# Welcome to the catalogd demo +make run + +# create a clustercatalog +kubectl apply -f $HOME/devel/tmp/operatorhubio-clustercatalog.yaml +# shows catalog +kubectl get clustercatalog -A +# waiting for clustercatalog to report ready status +time kubectl wait --for=condition=Unpacked clustercatalog/operatorhubio --timeout=1m + +# port forward the catalogd-service service to interact with the HTTP server serving catalog contents +(kubectl -n olmv1-system port-forward svc/catalogd-service 8080:443)& +sleep 5 + +# retrieve catalog as plaintext JSONlines +curl -k -vvv https://localhost:8080/catalogs/operatorhubio/api/v1/all --output /tmp/cat-content.json + +# advertise handling of compressed content +curl -vvv -k https://localhost:8080/catalogs/operatorhubio/api/v1/all -H 'Accept-Encoding: gzip' --output /tmp/cat-content.gz + +# let curl handle the compress/decompress for us +curl -vvv --compressed -k https://localhost:8080/catalogs/operatorhubio/api/v1/all --output /tmp/cat-content-decompressed.txt + +# show that there's no content change with changed format +diff /tmp/cat-content.json /tmp/cat-content-decompressed.txt + diff --git a/hack/demo/own-namespace-demo-script.sh b/hack/demo/own-namespace-demo-script.sh new file mode 100755 index 0000000000..611c6dfb05 --- /dev/null +++ b/hack/demo/own-namespace-demo-script.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# +# Welcome to the OwnNamespace install mode demo +# +set -e +trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT + +# install experimental CRDs with config field support +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" + +# wait for experimental CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + +# enable 'SingleOwnNamespaceInstallSupport' feature gate +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' + +# wait for operator-controller to become available +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +# create install namespace +kubectl create ns argocd-system + +# create installer service account +kubectl create serviceaccount -n argocd-system argocd-installer + +# give installer service account admin privileges (not for production environments) +kubectl create clusterrolebinding argocd-installer-crb --clusterrole=cluster-admin --serviceaccount=argocd-system:argocd-installer + +# install cluster extension in own namespace install mode (watch-namespace == install namespace == argocd-system) +cat ${DEMO_RESOURCE_DIR}/own-namespace-demo.yaml + +# apply cluster extension +kubectl apply -f ${DEMO_RESOURCE_DIR}/own-namespace-demo.yaml + +# wait for cluster extension installation to succeed +kubectl wait --for=condition=Installed clusterextension/argocd-operator --timeout="60s" + +# check argocd-operator controller deployment pod template olm.targetNamespaces annotation +kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.metadata.annotations.olm\.targetNamespaces}" + +# check for argocd-operator rbac in watch namespace +kubectl get roles,rolebindings -n argocd-system -o name + +# get controller service-account name +kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.spec.serviceAccount}" + +# check service account for role binding is the same as controller service-account +rolebinding=$(kubectl get rolebindings -n argocd-system -o name | grep 'argocd-operator' | head -n 1) +kubectl get -n argocd-system $rolebinding -o jsonpath='{.subjects}' | jq .[0] + +echo "Demo completed successfully!" + +# cleanup resources +echo "Cleaning up demo resources..." +kubectl delete clusterextension argocd-operator --ignore-not-found=true +kubectl delete namespace argocd-system --ignore-not-found=true +kubectl delete clusterrolebinding argocd-installer-crb --ignore-not-found=true + +# remove feature gate from deployment +echo "Removing feature gate from operator-controller..." +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' || true + +# restore standard CRDs +echo "Restoring standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/base.yaml" + +# wait for standard CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + +# wait for operator-controller to become available with standard config +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +echo "Demo cleanup completed!" diff --git a/hack/demo/resources/own-namespace-demo.yaml b/hack/demo/resources/own-namespace-demo.yaml new file mode 100644 index 0000000000..b22db7aa04 --- /dev/null +++ b/hack/demo/resources/own-namespace-demo.yaml @@ -0,0 +1,13 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: argocd-operator +spec: + namespace: argocd-system + serviceAccount: + name: argocd-installer + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 diff --git a/hack/demo/resources/single-namespace-demo.yaml b/hack/demo/resources/single-namespace-demo.yaml new file mode 100644 index 0000000000..9c1ac17f9f --- /dev/null +++ b/hack/demo/resources/single-namespace-demo.yaml @@ -0,0 +1,17 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: argocd-operator +spec: + namespace: argocd-system + serviceAccount: + name: argocd-installer + config: + configType: Inline + inline: + watchNamespace: argocd + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 diff --git a/hack/demo/resources/synthetic-user-perms/argocd-clusterextension.yaml b/hack/demo/resources/synthetic-user-perms/argocd-clusterextension.yaml new file mode 100644 index 0000000000..7eb5a7082b --- /dev/null +++ b/hack/demo/resources/synthetic-user-perms/argocd-clusterextension.yaml @@ -0,0 +1,13 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: argocd-operator +spec: + namespace: argocd-system + serviceAccount: + name: "olm.synthetic-user" + source: + sourceType: Catalog + catalog: + packageName: argocd-operator + version: 0.6.0 diff --git a/config/base/rbac/auth_proxy_role_binding.yaml b/hack/demo/resources/synthetic-user-perms/cegroup-admin-binding.yaml similarity index 56% rename from config/base/rbac/auth_proxy_role_binding.yaml rename to hack/demo/resources/synthetic-user-perms/cegroup-admin-binding.yaml index ec7acc0a1b..d0ab570f7b 100644 --- a/config/base/rbac/auth_proxy_role_binding.yaml +++ b/hack/demo/resources/synthetic-user-perms/cegroup-admin-binding.yaml @@ -1,12 +1,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: proxy-rolebinding + name: clusterextensions-group-admin-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: proxy-role + name: cluster-admin subjects: -- kind: ServiceAccount - name: controller-manager - namespace: system + - kind: Group + name: "olm:clusterextensions" diff --git a/hack/demo/resources/webhook-provider-certmanager/mutating-webhook-test.yaml b/hack/demo/resources/webhook-provider-certmanager/mutating-webhook-test.yaml new file mode 100644 index 0000000000..571940204a --- /dev/null +++ b/hack/demo/resources/webhook-provider-certmanager/mutating-webhook-test.yaml @@ -0,0 +1,7 @@ +apiVersion: webhook.operators.coreos.io/v1 +kind: webhooktest +metadata: + namespace: webhook-operator + name: mutating-webhook-test +spec: + valid: true diff --git a/hack/demo/resources/webhook-provider-certmanager/validating-webhook-test.yaml b/hack/demo/resources/webhook-provider-certmanager/validating-webhook-test.yaml new file mode 100644 index 0000000000..227ab8417f --- /dev/null +++ b/hack/demo/resources/webhook-provider-certmanager/validating-webhook-test.yaml @@ -0,0 +1,7 @@ +apiVersion: webhook.operators.coreos.io/v1 +kind: webhooktest +metadata: + namespace: webhook-operator + name: validating-webhook-test +spec: + valid: false diff --git a/hack/demo/resources/webhook-provider-certmanager/webhook-operator-catalog.yaml b/hack/demo/resources/webhook-provider-certmanager/webhook-operator-catalog.yaml new file mode 100644 index 0000000000..ff325c0646 --- /dev/null +++ b/hack/demo/resources/webhook-provider-certmanager/webhook-operator-catalog.yaml @@ -0,0 +1,9 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: webhook-operator-catalog +spec: + source: + type: Image + image: + ref: quay.io/operator-framework/webhook-operator-index:0.0.3 diff --git a/hack/demo/resources/webhook-provider-certmanager/webhook-operator-extension.yaml b/hack/demo/resources/webhook-provider-certmanager/webhook-operator-extension.yaml new file mode 100644 index 0000000000..19b7eceb08 --- /dev/null +++ b/hack/demo/resources/webhook-provider-certmanager/webhook-operator-extension.yaml @@ -0,0 +1,15 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: webhook-operator +spec: + namespace: webhook-operator + serviceAccount: + name: webhook-operator-installer + source: + catalog: + packageName: webhook-operator + version: 0.0.1 + selector: {} + upgradeConstraintPolicy: CatalogProvided + sourceType: Catalog diff --git a/hack/demo/single-namespace-demo-script.sh b/hack/demo/single-namespace-demo-script.sh new file mode 100755 index 0000000000..9702684152 --- /dev/null +++ b/hack/demo/single-namespace-demo-script.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# +# Welcome to the SingleNamespace install mode demo +# +set -e +trap 'echo "Demo ran into error"; trap - SIGTERM && kill -- -$$; exit 1' ERR SIGINT SIGTERM EXIT + +# install experimental CRDs with config field support +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/experimental.yaml" + +# wait for experimental CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + +# enable 'SingleOwnNamespaceInstallSupport' feature gate +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' + +# wait for operator-controller to become available +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +# create install namespace +kubectl create ns argocd-system + +# create installer service account +kubectl create serviceaccount -n argocd-system argocd-installer + +# give installer service account admin privileges (not for production environments) +kubectl create clusterrolebinding argocd-installer-crb --clusterrole=cluster-admin --serviceaccount=argocd-system:argocd-installer + +# create watch namespace +kubectl create namespace argocd + +# install cluster extension in single namespace install mode (watch namespace != install namespace) +cat ${DEMO_RESOURCE_DIR}/single-namespace-demo.yaml + +# apply cluster extension +kubectl apply -f ${DEMO_RESOURCE_DIR}/single-namespace-demo.yaml + +# wait for cluster extension installation to succeed +kubectl wait --for=condition=Installed clusterextension/argocd-operator --timeout="60s" + +# check argocd-operator controller deployment pod template olm.targetNamespaces annotation +kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.metadata.annotations.olm\.targetNamespaces}" + +# check for argocd-operator rbac in watch namespace +kubectl get roles,rolebindings -n argocd -o name + +# get controller service-account name +kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.spec.serviceAccount}" + +# check service account for role binding is the controller deployment service account +rolebinding=$(kubectl get rolebindings -n argocd -o name | grep 'argocd-operator' | head -n 1) +kubectl get -n argocd $rolebinding -o jsonpath='{.subjects}' | jq .[0] + +echo "Demo completed successfully!" + +# cleanup resources +echo "Cleaning up demo resources..." +kubectl delete clusterextension argocd-operator --ignore-not-found=true +kubectl delete namespace argocd-system argocd --ignore-not-found=true +kubectl delete clusterrolebinding argocd-installer-crb --ignore-not-found=true + +# remove feature gate from deployment +echo "Removing feature gate from operator-controller..." +kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]' || true + +# restore standard CRDs +echo "Restoring standard CRDs..." +kubectl apply -f "$(dirname "${BASH_SOURCE[0]}")/../../manifests/base.yaml" + +# wait for standard CRDs to be available +kubectl wait --for condition=established --timeout=60s crd/clusterextensions.olm.operatorframework.io + +# wait for operator-controller to become available with standard config +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +echo "Demo cleanup completed!" diff --git a/hack/demo/synthetic-user-cluster-admin-demo-script.sh b/hack/demo/synthetic-user-cluster-admin-demo-script.sh new file mode 100755 index 0000000000..4790e46e77 --- /dev/null +++ b/hack/demo/synthetic-user-cluster-admin-demo-script.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# +# Welcome to the SingleNamespace install mode demo +# +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +# enable 'SyntheticPermissions' feature +kubectl kustomize config/overlays/featuregate/synthetic-user-permissions | kubectl apply -f - + +# wait for operator-controller to become available +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +# create install namespace +kubectl create ns argocd-system + +# give cluster extension group cluster admin privileges - all cluster extensions installer users will be cluster admin +bat --style=plain ${DEMO_RESOURCE_DIR}/synthetic-user-perms/cegroup-admin-binding.yaml + +# apply cluster role binding +kubectl apply -f ${DEMO_RESOURCE_DIR}/synthetic-user-perms/cegroup-admin-binding.yaml + +# install cluster extension - for now .spec.serviceAccount = "olm.synthetic-user" +bat --style=plain ${DEMO_RESOURCE_DIR}/synthetic-user-perms/argocd-clusterextension.yaml + +# apply cluster extension +kubectl apply -f ${DEMO_RESOURCE_DIR}/synthetic-user-perms/argocd-clusterextension.yaml + +# wait for cluster extension installation to succeed +kubectl wait --for=condition=Installed clusterextension/argocd-operator --timeout="60s" diff --git a/hack/demo/webhook-provider-certmanager-demo-script.sh b/hack/demo/webhook-provider-certmanager-demo-script.sh new file mode 100755 index 0000000000..ba723ca6a8 --- /dev/null +++ b/hack/demo/webhook-provider-certmanager-demo-script.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +# +# Welcome to the webhook support with CertManager demo +# +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +# enable 'WebhookProviderCertManager' feature +kubectl kustomize config/overlays/featuregate/webhook-provider-certmanager | kubectl apply -f - + +# wait for operator-controller to become available +kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager + +# create webhook-operator catalog +cat ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/webhook-operator-catalog.yaml +kubectl apply -f ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/webhook-operator-catalog.yaml + +# wait for catalog to be serving +kubectl wait --for=condition=Serving clustercatalog/webhook-operator-catalog --timeout="60s" + +# create install namespace +kubectl create ns webhook-operator + +# create installer service account +kubectl create serviceaccount -n webhook-operator webhook-operator-installer + +# give installer service account admin privileges +kubectl create clusterrolebinding webhook-operator-installer-crb --clusterrole=cluster-admin --serviceaccount=webhook-operator:webhook-operator-installer + +# install webhook operator clusterextension +cat ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/webhook-operator-extension.yaml + +# apply cluster extension +kubectl apply -f ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/webhook-operator-extension.yaml + +# wait for cluster extension installation to succeed +kubectl wait --for=condition=Installed clusterextension/webhook-operator --timeout="60s" + +# wait for webhook-operator deployment to become available and back the webhook service +kubectl wait --for=condition=Available -n webhook-operator deployments/webhook-operator-webhook + +# demonstrate working validating webhook +cat ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/validating-webhook-test.yaml + +# resource creation should be rejected by the validating webhook due to bad attribute value +kubectl apply -f ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/validating-webhook-test.yaml + +# demonstrate working mutating webhook +cat ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/mutating-webhook-test.yaml + +# apply resource +kubectl apply -f ${DEMO_RESOURCE_DIR}/webhook-provider-certmanager/mutating-webhook-test.yaml + +# get webhooktest resource in v1 schema - resource should have new .spec.mutate attribute +kubectl get webhooktest.v1.webhook.operators.coreos.io -n webhook-operator mutating-webhook-test -o yaml + +# demonstrate working conversion webhook by getting webhook test resource in v2 schema - the .spec attributes should now be under the .spec.conversion stanza +kubectl get webhooktest.v2.webhook.operators.coreos.io -n webhook-operator mutating-webhook-test -o yaml + +# this concludes the webhook support demo - Thank you! diff --git a/hack/kind-config/containerd/certs.d/docker-registry.operator-controller-e2e.svc.cluster.local:5000/hosts.toml b/hack/kind-config/containerd/certs.d/docker-registry.operator-controller-e2e.svc.cluster.local:5000/hosts.toml new file mode 100644 index 0000000000..b0a5eb47fc --- /dev/null +++ b/hack/kind-config/containerd/certs.d/docker-registry.operator-controller-e2e.svc.cluster.local:5000/hosts.toml @@ -0,0 +1,3 @@ +[host."https://localhost:30000/"] + capabilities = ["pull", "resolve"] + skip_verify = true diff --git a/hack/kind-config/containerd/certs.d/go.mod b/hack/kind-config/containerd/certs.d/go.mod new file mode 100644 index 0000000000..2cf82571b4 --- /dev/null +++ b/hack/kind-config/containerd/certs.d/go.mod @@ -0,0 +1,5 @@ +module hack-cert.d +// This file is present in the certs.d directory to ensure that +// certs.d/host:port directories are not included in the main go +// module. Go modules are not allowed to contain files with ':' +// in their name. diff --git a/hack/test/build-push-e2e-catalog.sh b/hack/test/build-push-e2e-catalog.sh deleted file mode 100755 index 9fd1a9d6b2..0000000000 --- a/hack/test/build-push-e2e-catalog.sh +++ /dev/null @@ -1,66 +0,0 @@ -#! /bin/bash - -set -o errexit -set -o nounset -set -o pipefail - -help=" -build-push-e2e-catalog.sh is a script to build and push the e2e catalog image using kaniko. -Usage: - build-push-e2e-catalog.sh [NAMESPACE] [TAG] - -Argument Descriptions: - - NAMESPACE is the namespace the kaniko Job should be created in - - TAG is the full tag used to build and push the catalog image -" - -if [[ "$#" -ne 2 ]]; then - echo "Illegal number of arguments passed" - echo "${help}" - exit 1 -fi - -namespace=$1 -image=$2 -tag=${image##*:} - -echo "${namespace}" "${image}" "${tag}" - -kubectl create configmap -n "${namespace}" --from-file=testdata/catalogs/test-catalog-${tag}.Dockerfile operator-controller-e2e-${tag}.dockerfile -kubectl create configmap -n "${namespace}" --from-file=testdata/catalogs/test-catalog-${tag} operator-controller-e2e-${tag}.build-contents - -kubectl apply -f - << EOF -apiVersion: batch/v1 -kind: Job -metadata: - name: "kaniko-${tag}" - namespace: "${namespace}" -spec: - template: - spec: - containers: - - name: kaniko-${tag} - image: gcr.io/kaniko-project/executor:latest - args: ["--dockerfile=/workspace/test-catalog-${tag}.Dockerfile", - "--context=/workspace/", - "--destination=${image}", - "--skip-tls-verify"] - volumeMounts: - - name: dockerfile - mountPath: /workspace/ - - name: build-contents - mountPath: /workspace/test-catalog-${tag}/ - restartPolicy: Never - volumes: - - name: dockerfile - configMap: - name: operator-controller-e2e-${tag}.dockerfile - items: - - key: test-catalog-${tag}.Dockerfile - path: test-catalog-${tag}.Dockerfile - - name: build-contents - configMap: - name: operator-controller-e2e-${tag}.build-contents -EOF - -kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${tag} --timeout=60s diff --git a/hack/test/e2e-coverage.sh b/hack/test/e2e-coverage.sh index a5107ae12c..49c8db3d7c 100755 --- a/hack/test/e2e-coverage.sh +++ b/hack/test/e2e-coverage.sh @@ -2,19 +2,25 @@ set -euo pipefail -COVERAGE_OUTPUT="${COVERAGE_OUTPUT:-${ROOT_DIR}/coverage/e2e.out}" +COVERAGE_NAME="${COVERAGE_NAME:-e2e}" OPERATOR_CONTROLLER_NAMESPACE="olmv1-system" OPERATOR_CONTROLLER_MANAGER_DEPLOYMENT_NAME="operator-controller-controller-manager" + +CATALOGD_NAMESPACE="olmv1-system" +CATALOGD_MANAGER_DEPLOYMENT_NAME="catalogd-controller-manager" + COPY_POD_NAME="e2e-coverage-copy-pod" # Create a temporary directory for coverage -COVERAGE_DIR=${ROOT_DIR}/coverage/e2e +COVERAGE_OUTPUT=${ROOT_DIR}/coverage/${COVERAGE_NAME}.out +COVERAGE_DIR=${ROOT_DIR}/coverage/${COVERAGE_NAME} rm -rf ${COVERAGE_DIR} && mkdir -p ${COVERAGE_DIR} # Coverage-instrumented binary produces coverage on termination, # so we scale down the manager before gathering the coverage kubectl -n "$OPERATOR_CONTROLLER_NAMESPACE" scale deployment/"$OPERATOR_CONTROLLER_MANAGER_DEPLOYMENT_NAME" --replicas=0 +kubectl -n "$CATALOGD_NAMESPACE" scale deployment/"$CATALOGD_MANAGER_DEPLOYMENT_NAME" --replicas=0 # Wait for the copy pod to be ready kubectl -n "$OPERATOR_CONTROLLER_NAMESPACE" wait --for=condition=ready pod "$COPY_POD_NAME" diff --git a/hack/test/install-prometheus.sh b/hack/test/install-prometheus.sh new file mode 100755 index 0000000000..c9d7e0b1c4 --- /dev/null +++ b/hack/test/install-prometheus.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +source ".bingo/variables.env" + +set -euo pipefail + +help="install-prometheus.sh is used to set up prometheus monitoring for e2e testing. +Usage: + install-prometheus.sh [PROMETHEUS_NAMESPACE] [PROMETHEUS_VERSION] [GIT_VERSION] +" + +if [[ "$#" -ne 3 ]]; then + echo "Illegal number of arguments passed" + echo "${help}" + exit 1 +fi + +PROMETHEUS_NAMESPACE="$1" +PROMETHEUS_VERSION="$2" +GIT_VERSION="$3" + +TMPDIR="$(mktemp -d)" +trap 'echo "Cleaning up $TMPDIR"; rm -rf "$TMPDIR"' EXIT + +echo "Downloading Prometheus resources..." +curl -s "https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/refs/tags/$%7BPROMETHEUS_VERSION%7D/kustomization.yaml" > "${TMPDIR}/kustomization.yaml" +curl -s "https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/refs/tags/$%7BPROMETHEUS_VERSION%7D/bundle.yaml" > "${TMPDIR}/bundle.yaml" + +echo "Patching namespace to ${PROMETHEUS_NAMESPACE}..." +(cd "$TMPDIR" && ${KUSTOMIZE} edit set namespace "$PROMETHEUS_NAMESPACE") + +echo "Applying Prometheus base..." +kubectl apply -k "$TMPDIR" --server-side + +echo "Waiting for Prometheus Operator pod to become ready..." +kubectl wait --for=condition=Ready pod -n "$PROMETHEUS_NAMESPACE" -l app.kubernetes.io/name=prometheus-operator + +echo "Applying prometheus Helm chart..." +${HELM} template prometheus helm/prometheus | sed "s/cert-git-version/cert-${VERSION}/g" | kubectl apply -f - + +echo "Waiting for metrics scraper to become ready..." +kubectl wait --for=create pods -n "$PROMETHEUS_NAMESPACE" prometheus-prometheus-0 --timeout=60s +kubectl wait --for=condition=Ready pods -n "$PROMETHEUS_NAMESPACE" prometheus-prometheus-0 --timeout=120s + +echo "Prometheus deployment completed successfully." diff --git a/hack/test/pre-upgrade-setup.sh b/hack/test/pre-upgrade-setup.sh index 8eed4c81d0..669f9da37f 100755 --- a/hack/test/pre-upgrade-setup.sh +++ b/hack/test/pre-upgrade-setup.sh @@ -20,17 +20,16 @@ TEST_CLUSTER_CATALOG_NAME=$2 TEST_CLUSTER_EXTENSION_NAME=$3 kubectl apply -f - << EOF -apiVersion: olm.operatorframework.io/v1alpha1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: name: ${TEST_CLUSTER_CATALOG_NAME} spec: source: - type: image + type: Image image: ref: ${TEST_CATALOG_IMG} - pollInterval: 24h - insecureSkipTLSVerify: true + pollIntervalMinutes: 1440 EOF kubectl apply -f - < [!WARNING] +> These scripts are intended to help users navigate the catalog and produce installation RBAC until reliable tooling is available for OLM v1, +> and to document the process in code for contributors. These scripts are not officially supported. +> They are not meant to be used in production environments. +--- + +### Prerequisites + +To execute the scripts, the following tools are required: + + * [jq](https://jqlang.github.io/jq/) to filter catalog data + * [yq](https://mikefarah.gitbook.io/yq/) to parse YAML + * [kubectl](https://kubernetes.io/docs/reference/kubectl/) to interact with the cluster running OLM v1 + * [wget](https://www.gnu.org/software/wget/) to download the catalog data + * A container runtime, such as [podman](https://podman.io/) or [docker](https://www.docker.com/) to interact with bundle images. + +#### Container Runtime + +By default, the scripts use `podman` or `docker` as the container runtime. +If you use another container runtime, set the `CONTAINER_RUNTIME` environment variable to the path of the container runtime binary. + +### Tools + +--- +> [!NOTE] +> All examples assume that the current working directory is the `hack/tools/catalogs` directory. +--- + +#### download-catalog + +Download a catalog from a served ClusterCatalog running on a cluster reachable by `kubectl`. + +Example: + + ```terminal + # Download the catalog from the operatorhubio ClusterCatalog + ./download-catalog operatorhubio + ``` + +The downloaded catalog is saved to -catalog.json in the current directory. + +#### list-compatible-bundles + +List (potential) OLM v1 compatible bundles from the catalog. + +Not all registry+v1 bundles made for OLM v0 are compatible with OLM v1. +Compatible bundles must meet the following criteria: + * Support for the 'AllNamespaces' install mode + * No webhooks + * No dependencies on other packages of GVKs + * The operator does not make use of OLM v0's [`OperatorCondition`](https://olm.operatorframework.io/docs/concepts/crds/operatorcondition/) API + + +For more information, see [OLM v1 limitations](../../../docs/refs/olm-v1-limitations.md). + +For some bundles, some of this criteria can only be determined by inspecting the contents bundle image. The script will return all bundles that are potentially compatible. + +Examples: + + ``` terminal + # List (potentially) OLM v1 compatible bundles from the operatorhubio catalog + ./list-compatible-bundles < operatorhubio-catalog.json + ``` + + ``` terminal + # List (potentially) OLM v1 compatible bundles that contain 'argco' in the package name + # -r can be used with any regex supported by jq + ./list-compatible-bundles -r 'argocd' < operatorhubio-catalog.json + ``` + +#### find-bundle-image + +Find the image for a bundle in the catalog. + +Example: + + ``` terminal + # Get the image for the argocd-operator v0.6.0 bundle from the operatorhubio catalog + ./find-bundle-image argocd-operator 0.6.0 < operatorhubio-catalog.json + ``` + +#### unpack-bundle + +Unpack a bundle image to a directory. + +Example: + + ``` terminal + # Unpack the argocd-operator v0.6.0 bundle image to a temporary directory + ./unpack-bundle quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3 + ``` + + ``` terminal + # Unpack the argocd-operator v0.6.0 bundle image to a specific directory + ./unpack-bundle quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3 -o argocd-manifests + ``` + +#### is-bundle-supported + +Check if a bundle is supported by OLM v1 by inspecting the unpacked bundle manifests. + + +For more information on bundle support, see [OLM v1 limitations](../../../docs/refs/olm-v1-limitations.md). + +Example: + + ``` terminal + # Check if the argocd-operator v0.6.0 bundle from the operatorhubio catalog is supported by OLM v1 + ./is-bundle-supported argocd-manifests + ``` + + ``` terminal + # Find bundle image, unpack, and verify support in one command + ./find-bundle-image argocd-operator 0.6.0 < operatorhubio-catalog.json | ./unpack-bundle | ./is-bundle-supported + ``` + +#### generate-manifests + +Generate RBAC or installation manifests for a bundle. The generated manifests can be templates or fully rendered manifests. + +The following options can be used to override resource naming defaults: + -n Namespace where the extension is installed + -e - Name of the extension + -cr - Name of the cluster role + -r - Name of the role + -s - Name of the service account + --template - Generate template manifests + +Default resource name format: + * Namespace: -system + * Extension name: + * ClusterRole name: -cluster-role + * Role name: -installer-role + * ServiceAccount name: -installer + * ClusterRoleBinding name: -binding + * RoleBinding name: -binding + +Use `--template` to generate templated manifests that can be customized before applying to the cluster. +Template manifests will contain the following template variables: + +Template Variables: +* `${NAMESPACE}` - Namespace where the extension is installed +* `${EXTENSION_NAME}` - Name of the extension +* `${CLUSTER_ROLE_NAME}` - Name of the cluster role +* `${ROLE_NAME}` - Name of the role +* `${SERVICE_ACCOUNT_NAME}` - Name of the service account + +Examples: + + ``` terminal + # Generate installation manifests for the argocd-operator v0.6.0 bundle from the operatorhubio catalog + ./generate-manifests install argocd-operator 0.6.0 < operatorhubio-catalog.json + ``` + + ``` terminal + # Generate templated installation manifests for the argocd-operator v0.6.0 bundle from the operatorhubio catalog + generate-manifests install argocd-operator 0.6.0 --template < operatorhubio-catalog.json + ``` + + ``` terminal + # Generate RBAC manifests for the argocd-operator v0.6.0 bundle from the operatorhubio catalog + generate-manifests rbac argocd-operator 0.6.0 < operatorhubio-catalog.json + ``` + + ``` terminal + # Generate templated RBAC manifests for the argocd-operator v0.6.0 bundle from the operatorhubio catalog + generate-manifests rbac argocd-operator 0.6.0 --template < operatorhubio-catalog.json + ``` diff --git a/hack/tools/catalogs/download-catalog b/hack/tools/catalogs/download-catalog new file mode 100755 index 0000000000..0c046f3620 --- /dev/null +++ b/hack/tools/catalogs/download-catalog @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +SCRIPT_ROOT=$(dirname "$(realpath "$0")") +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check required tools are installed +assert-commands kubectl jq wget + +# ClusterCatalog coordinates +: "${CATALOGD_CATALOGD_SERVICE_NAMESPACE:=olmv1-system}" +: "${CATALOGD_SERVICE_NAME:=catalogd-service}" +: "${CATALOGD_SERVICE_PORT:=443}" # Assumes the service uses HTTPS on port 443 +: "${CATALOGD_LOCAL_SERVICE_PORT:=8001}" + +echo "Namespace: $CATALOGD_CATALOGD_SERVICE_NAMESPACE" +echo "Service Name: $CATALOGD_SERVICE_NAME" +echo "Service Port: $CATALOGD_SERVICE_PORT" +echo "Local Service Port: $CATALOGD_LOCAL_SERVICE_PORT" + +# Display usage +usage() { + print-banner + echo "" + echo "Usage: $0 " + echo "" + echo "Download catalog from a ClusterCatalog in a cluster reachable from KUBECONFIG" + echo "Downloaded catalog will be saved as -catalog.json" + echo "" + echo "Example:" + echo " $0 operatorhubio" +} + +# Check if catalog name is provided +if [ -z "$1" ]; then + usage + exit 1 +fi + +CATALOG_NAME="$1" + +# Check if the clustercatalog resource exists +echo "Checking if ClusterCatalog $CATALOG_NAME exists..." +CLUSTER_CATALOG=$(kubectl get clustercatalog "$CATALOG_NAME" -o json 2>/dev/null) +if [ -z "$CLUSTER_CATALOG" ]; then + echo "ClusterCatalog $CATALOG_NAME does not exist." + exit 1 +fi + +# Check if the Serving condition is true +UNPACKED_CONDITION=$(echo "$CLUSTER_CATALOG" | jq -r '.status.conditions[]? // [] | select(.type=="Serving") | .status') +if [ "$UNPACKED_CONDITION" != "True" ]; then + echo "ClusterCatalog $CATALOG_NAME is not ready yet." + exit 1 +fi + +# Construct the API URL from the ClusterCatalog status +CATALOG_API_URL=$(echo "$CLUSTER_CATALOG" | jq -r '.status.urls.base') +if [ -z "$CATALOG_API_URL" ]; then + echo "ClusterCatalog $CATALOG_NAME does not express API endpoint in '.status.urls.base'." + exit 1 +fi +# Assemble the v1 API URL from the base endpoint +CATALOG_CONTENT_URL="${CATALOG_API_URL}/api/v1/all" + +# Start port forwarding +echo "Starting kubectl port-forward to $CATALOGD_SERVICE_NAME on port $CATALOGD_LOCAL_SERVICE_PORT..." +kubectl port-forward -n "$CATALOGD_CATALOGD_SERVICE_NAMESPACE" svc/"$CATALOGD_SERVICE_NAME" "$CATALOGD_LOCAL_SERVICE_PORT:$CATALOGD_SERVICE_PORT" &>/dev/null & +PORT_FORWARD_PID=$! + +# Poll the service until it responds or timeout after 30 seconds +timeout=30 +while ! curl -s "http://localhost:${CATALOGD_LOCAL_SERVICE_PORT}" >/dev/null; do + timeout=$((timeout - 1)) + if [ $timeout -le 0 ]; then + echo "Port forwarding failed to start within 30 seconds." + kill $PORT_FORWARD_PID + exit 1 + fi + sleep 1 +done + +# Modify the catalogd service endpoint URL to hit localhost: +LOCAL_CONTENT_URL=${CATALOG_CONTENT_URL//https:\/\/$CATALOGD_SERVICE_NAME.$CATALOGD_CATALOGD_SERVICE_NAMESPACE.svc/https:\/\/localhost:$CATALOGD_LOCAL_SERVICE_PORT} +echo "Calculated catalogd API pathcontent URL: $CATALOG_CONTENT_URL" +echo "Using local port: $CATALOGD_LOCAL_SERVICE_PORT" +echo "Using local content URL: $LOCAL_CONTENT_URL" + +# shellcheck disable=SC2001 +# Download the catalog using wget +echo "Downloading catalog from $LOCAL_CONTENT_URL..." +wget --no-check-certificate "$LOCAL_CONTENT_URL" -O "${CATALOG_NAME}-catalog.json" + +# Stop the port forwarding +echo "Stopping kubectl port-forward..." +kill $PORT_FORWARD_PID + +echo "Catalog downloaded to ${CATALOG_NAME}-catalog.json" diff --git a/hack/tools/catalogs/find-bundle-image b/hack/tools/catalogs/find-bundle-image new file mode 100755 index 0000000000..9394e6b72e --- /dev/null +++ b/hack/tools/catalogs/find-bundle-image @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") + +source "${SCRIPT_ROOT}/lib/bundle.sh" +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check required tools are installed +assert-commands jq + +usage() { + print-banner + echo "" + echo "Usage: $0 " + echo "" + echo "Find the bundle image for a package in a catalog in " + echo "" + echo "Example:" + echo " $0 argocd-operator 0.6.0 < operatorhubio-catalog.json" +} + +if [ "$#" -lt 2 ]; then + usage + exit 1 +fi + +package_name="$1" +package_version="$2" + +# Find bundle image +image="$(cat - | get-bundle-image "${package_name}" "${package_version}")" +echo "${image}" diff --git a/hack/tools/catalogs/generate-manifests b/hack/tools/catalogs/generate-manifests new file mode 100755 index 0000000000..02c48a7aec --- /dev/null +++ b/hack/tools/catalogs/generate-manifests @@ -0,0 +1,182 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") + +source "${SCRIPT_ROOT}/lib/unpack.sh" +source "${SCRIPT_ROOT}/lib/collect-rbac.sh" +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check there is a container runtime (podman, or docker) +# If neither are found, check CONTAINER_RUNTIME is set and exists in PATH +assert-container-runtime + +# Check required tools are installed +assert-commands jq + +usage() { + print-banner + echo "" + echo "Usage:" + echo "" + echo "Generate installation manifests" + echo "$0 install [-n namespace] [-e cluster-extension-name] [-cr cluster-role-name] [-r role-name] [-s service-account-name] [--template]" + echo "" + echo "Generate RBAC manifests" + echo "$0 rbac [-n namespace] [-e cluster-extension-name] [-cr cluster-role-name] [-r role-name] [-s service-account-name] [--template]" + echo "" + echo "Generate installation or RBAC manifests for a registry+v1 package given an FBC catalog in stdin" + echo "" + echo "Options:" + echo " -n - Namespace where the extension is installed" + echo " -e - Name of the extension" + echo " -cr - Name of the cluster role" + echo " -r - Name of the role" + echo " -s - Name of the service account" + echo " --template - Generate template manifests" + echo "" + echo "Template Variables:" + echo " * \${NAMESPACE} - Namespace where the extension is installed" + echo " * \${EXTENSION_NAME} - Name of the extension" + echo " * \${CLUSTER_ROLE_NAME} - Name of the cluster role" + echo " * \${ROLE_NAME} - Name of the role" + echo " * \${SERVICE_ACCOUNT_NAME} - Name of the service account" + echo "" + echo "Default Resource Name Format:" + echo " * Namespace: -system" + echo " * Extension name: " + echo " * ClusterRole name: -cluster-role" + echo " * Role name: -installer-role" + echo " * ServiceAccount name: -installer" + echo " * ClusterRoleBinding name: -binding" + echo " * RoleBinding name: -binding" + echo "" + echo "Examples:" + echo " # Generate installation manifests for the argocd-operator package" + echo " $0 install argocd-operator 0.6.0 < operatorhubio-catalog.json" + echo "" + echo " # Generate RBAC manifests for the argocd-operator package" + echo " $0 rbac argocd-operator 0.6.0 < operatorhubio-catalog.json" + echo "" + echo " # Generate templated installation manifests for the argocd-operator package" + echo " $0 install argocd-operator 0.6.0 --template < operatorhubio-catalog.json" + echo "" + echo " # Generate templated RBAC manifests for the argocd-operator package" + echo " $0 rbac argocd-operator 0.6.0 --template < operatorhubio-catalog.json" + echo "" + echo "WARNING: This script is a stopgap solution until proper tools are available in OLMv1 - it is not guaranteed to work with all packages." +} + +# Check for at least 3 arguments +if [ "$#" -lt 3 ]; then + usage + exit 1 +fi + +# Command and package details +COMMAND=$1 +export PACKAGE_NAME=$2 +export PACKAGE_VERSION=$3 + +# Initialize environment variables with template defaults +export NAMESPACE="\${NAMESPACE}" +export EXTENSION_NAME="\${EXTENSION_NAME}" +export CLUSTER_ROLE_NAME="\${CLUSTER_ROLE_NAME}" +export ROLE_NAME="\${ROLE_NAME}" +export SERVICE_ACCOUNT_NAME="\${SERVICE_ACCOUNT_NAME}" +export DEBUG=false +template=false + +# Parse optional arguments +shift 3 +while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + -n) + export NAMESPACE="$2" + shift 2 + ;; + -e) + export EXTENSION_NAME="$2" + shift 2 + ;; + -cr) + export CLUSTER_ROLE_NAME="$2" + shift 2 + ;; + -r) + export ROLE_NAME="$2" + shift 2 + ;; + -s) + export SERVICE_ACCOUNT_NAME="$2" + shift 2 + ;; + --template) + template=true + shift + ;; + --debug) + DEBUG=1 + shift + ;; + *) + echo "Unknown option $1" + usage + exit 1 + ;; + esac +done + +# Apply default values only to unset parameters if --template is not set +if [ "$template" = false ]; then + [ "$EXTENSION_NAME" == "\${EXTENSION_NAME}" ] && export EXTENSION_NAME="${PACKAGE_NAME}" + [ "$NAMESPACE" == "\${NAMESPACE}" ] && export NAMESPACE="${EXTENSION_NAME}-system" + [ "$SERVICE_ACCOUNT_NAME" == "\${SERVICE_ACCOUNT_NAME}" ] && export SERVICE_ACCOUNT_NAME="${PACKAGE_NAME}-installer" + [ "$CLUSTER_ROLE_NAME" == "\${CLUSTER_ROLE_NAME}" ] && export CLUSTER_ROLE_NAME="${SERVICE_ACCOUNT_NAME}-cluster-role" + [ "$ROLE_NAME" == "\${ROLE_NAME}" ] && export ROLE_NAME="${SERVICE_ACCOUNT_NAME}-installer-role" +fi + +# Output the set environment variables for confirmation +debug "Environment variables set:" +debug "NAMESPACE=${NAMESPACE}" +debug "EXTENSION_NAME=${EXTENSION_NAME}" +debug "CLUSTER_ROLE_NAME=${CLUSTER_ROLE_NAME}" +debug "ROLE_NAME=${ROLE_NAME}" +debug "SERVICE_ACCOUNT_NAME=${SERVICE_ACCOUNT_NAME}" + +# Find bundle image +image="$(cat - | get-bundle-image "${PACKAGE_NAME}" "${PACKAGE_VERSION}")" + +# Unpack and close container +bundle_manifest_dir="$("${SCRIPT_ROOT}/unpack-bundle" "${image}")" + +# Derive rbac from bundle manifests +collect_installer_rbac "${bundle_manifest_dir}" +echo "Done" >&2 + +# Example output or further processing based on command +case "${COMMAND}" in + install) + generate_install_manifests | envsubst + ;; + rbac) + generate_rbac_manifests | envsubst + ;; + *) + echo "Unknown command ${COMMAND}" + usage + exit 1 + ;; +esac + +# Clean up manifest directory +if [ "${DEBUG,,}" != "false" ]; then + debug "Skipping cleanup of manifest directory: ${bundle_manifest_dir}" +else + rm -rf "${bundle_manifest_dir}" +fi \ No newline at end of file diff --git a/hack/tools/catalogs/is-bundle-supported b/hack/tools/catalogs/is-bundle-supported new file mode 100755 index 0000000000..b197b53102 --- /dev/null +++ b/hack/tools/catalogs/is-bundle-supported @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") + +source "${SCRIPT_ROOT}/lib/unpack.sh" +source "${SCRIPT_ROOT}/lib/collect-rbac.sh" +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check required tools are installed +assert-commands jq + +usage() { + print-banner + echo "" + echo "Usage: $0 " + echo "" + echo "Check if a bundle is supported by OLM v1 given a bundle manifest directory" + echo "" + echo "Example:" + echo " $0 " +} + +# Must have a single argument +if [ -z "$1" ]; then + if [ -t 0 ]; then + echo "Error: Docker image name is required." + usage + exit 1 + else + read -r manifest_dir + fi +else + manifest_dir="$1" +fi + + # Check the bundle is supported +assert-bundle-supported "${manifest_dir}" + +echo "Bundle is supported by OLM v1" >&2 +echo "true" \ No newline at end of file diff --git a/hack/tools/catalogs/lib/bundle.sh b/hack/tools/catalogs/lib/bundle.sh new file mode 100644 index 0000000000..2adfc260f0 --- /dev/null +++ b/hack/tools/catalogs/lib/bundle.sh @@ -0,0 +1,194 @@ +# Library of functions for interacting with bundles and their manifests + +# SCRIPT_ROOT is the root of the script +source "$(dirname "${BASH_SOURCE[0]}")/hash.sh" + +# Given package and version grabs the bundle image from stdin FBC stream +get-bundle-image(){ + local package_name="${1}" + local package_version="${2}" + local image + image=$(jq -r --arg pkg "$package_name" --arg ver "$package_version" \ + 'select(.schema == "olm.bundle" and (.properties[] | select(.type == "olm.package" and .value.packageName == $pkg and .value.version == $ver))) | .image') + + if [ -z "$image" ]; then + echo "ERROR: No matching image found for package '$package_name' with version '$package_version'." >&2 + exit 1 + fi + + echo "${image}" +} + +is-all-namespace-mode-enabled() { + local csv="${1}" + local valid + # Note: The 'and true' is to ensure that the expression evaluates to true or false + # Without it, yq will return the matched value. This is done for succinctness. Without the 'and true', + # the same behavior could be achieved by checking if the output is non-empty. + valid=$(yq eval '(.spec.installModes[] | select(.type == "AllNamespaces" and .supported == true)) and true' "${csv}") + echo "${valid}" +} + +does-not-have-webhooks() { + local csv="${1}" + local valid + valid=$(yq eval '((.spec.webhookdefinitions == null) or (.spec.webhookdefinitions | length == 0))' "${csv}") + echo "${valid}" +} + +does-not-have-dependencies() { + local csv="${1}" + local valid + valid=$(yq eval '((.spec.customresourcedefinitions.required == null) or (.spec.customresourcedefinitions.required | length == 0))' "${csv}") + echo "${valid}" +} + +is-crd-version-supported() { + local manifest_dir="${1}" + local valid=true + while IFS= read -r resource_file; do + version=$(yq eval '.apiVersion' "$resource_file") + if [ "${version}" != "apiextensions.k8s.io/v1" ]; then + valid="${version}" + break + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: CustomResourceDefinition" {} \;) + echo "$valid" +} + +is-bundle-supported() { + local manifest_dir="${1}" + csv="$(find_csv "${manifest_dir}")" + + crd_version="$(is-crd-version-supported "${manifest_dir}")" + + if [ "$(is-all-namespace-mode-enabled "${csv}")" != "true" ]; then + echo "Bundle not supported: AllNamespaces install mode is disabled" >&2 + echo "false" + elif [ "$(does-not-have-webhooks "${csv}")" != "true" ]; then + echo "Bundle not supported: contains webhooks" >&2 + echo "false" + elif [ "$(does-not-have-dependencies "${csv}")" != "true" ]; then + echo "Bundle not supported: contains dependencies" >&2 + echo "false" + elif [ "${crd_version}" != "true" ]; then + echo "Bundle not supported: unsupported CRD api version (${crd_version})" >&2 + echo "false" + fi + + echo "true" +} + +# Function to validate the bundle is supported +assert-bundle-supported() { + local manifest_dir="${1}" + if [ "$(is-bundle-supported "${manifest_dir}")" != "true" ]; then + exit 1 + fi +} + +# Function to get all resource names for a particular kind +# from the manifest directory +collect_resource_names() { + local manifest_dir="${1}" + local kind="${2}" + local resource_names=() + while IFS= read -r resource_file; do + name=$(yq eval -r '.metadata.name' "$resource_file") + if [ -n "$name" ]; then + resource_names+=("$name") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "${resource_names[@]}" +} + +# Function that collects all the rules for all the ClusterRole manifests +# shipped with the bundle +collect_manifest_cluster_role_perms() { + local manifest_dir="${1}" + local kind="ClusterRole" + local all_cr_rules="[]" + + while IFS= read -r resource_file; do + # Extract the entire rules array from the current file and ensure it's treated as a valid JSON array + cr_rules=$(yq eval -o=json -r '.rules // []' "$resource_file") + # Validate and merge the current rules array with the cumulative rules array + if jq -e . >/dev/null 2>&1 <<<"$cr_rules"; then + all_cr_rules=$(jq -c --argjson existing "$all_cr_rules" --argjson new "$cr_rules" '$existing + $new' <<<"$all_cr_rules") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "$all_cr_rules" +} + +# Function that collects all the rules for all the Role manifests +# shipped with the bundle +collect_manifest_role_perms() { + local manifest_dir="${1}" + local kind="Role" + local all_cr_rules="[]" + + while IFS= read -r resource_file; do + # Extract the entire rules array from the current file and ensure it's treated as a valid JSON array + cr_rules=$(yq eval -o=json -r '.rules // []' "$resource_file") + # Validate and merge the current rules array with the cumulative rules array + if jq -e . >/dev/null 2>&1 <<<"$cr_rules"; then + all_cr_rules=$(jq -c --argjson existing "$all_cr_rules" --argjson new "$cr_rules" '$existing + $new' <<<"$all_cr_rules") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "$all_cr_rules" +} + +# Function to get the apiGroup for a named resource of a given kind +# from the manifests dir +get_api_group() { + local dir_path="$1" + local kind="$2" + local name="$3" + + # Find the file containing the specified kind and name + local file + file=$(grep -rl "kind: $kind" "$dir_path" | xargs grep -l "name: $name") + + # Extract the apiGroup from the found file + if [ -n "$file" ]; then + local api_group + api_group=$(yq eval '.apiVersion' "$file" | awk -F'/' '{print $1}') + echo "$api_group" + fi +} + +# Function to get the generated clusterrole resource names +generated_cluster_role_names() { + local csvFile="${1}" + local generated_cluster_role_names=() + csv_name=$(yq eval -r '.metadata.name' "${csvFile}") + cperms=$(yq eval -o=json -r '.spec.install.spec.clusterPermissions? // []' "$csvFile" | jq -c '.[] | {serviceAccountName, rules: [.rules[] | {verbs, apiGroups, resources, resourceNames, nonResourceURLs} | with_entries(select(.value != null and .value != []))]}') + rbacPerms=$(yq eval -o=json -r '.spec.install.spec.permissions? // []' "$csvFile" | jq -c '.[] | {serviceAccountName, rules: [.rules[] | {verbs, apiGroups, resources, resourceNames, nonResourceURLs} | with_entries(select(.value != null and .value != []))]}') + allPerms=("${cperms[@]}" "${rbacPerms[@]}") + for perm in "${allPerms[@]}"; do + sa=$(echo "$perm" | yq eval -r '.serviceAccountName') + generated_name="$(generate_name "${csv_name}-${sa}" "${perm}")" + generated_cluster_role_names+=("${generated_name}") + done + echo "${generated_cluster_role_names[@]}" +} + +# Get CSV from manifest directory +find_csv() { + local manifest_dir="${1}" + local csv_files + + # Use grep -l to find files containing "kind: ClusterServiceVersion" + csv_files=$(grep -l "kind: ClusterServiceVersion" "$manifest_dir"/*.yaml) + + # Check if multiple CSV files are found + if [ "$(echo "$csv_files" | wc -l)" -gt 1 ]; then + echo "Error: Multiple CSV files found in ${manifest_dir}." + return 1 + elif [ -z "$csv_files" ]; then + echo "Error: No CSV file found in ${manifest_dir}." + return 1 + else + echo "${csv_files}" + fi +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/collect-rbac.sh b/hack/tools/catalogs/lib/collect-rbac.sh new file mode 100644 index 0000000000..33ed54da3e --- /dev/null +++ b/hack/tools/catalogs/lib/collect-rbac.sh @@ -0,0 +1,144 @@ +# Library of functions for collecting RBAC from manifests for the purposes of generating +# The required RBAC for the cluster extension installation +source "$(dirname "${BASH_SOURCE[0]}")/utils.sh" +source "$(dirname "${BASH_SOURCE[0]}")/rbac.sh" +source "$(dirname "${BASH_SOURCE[0]}")/manifests.sh" +source "$(dirname "${BASH_SOURCE[0]}")/bundle.sh" + +# Function to add the specified rules +add_required_rules() { + local finalizer_perm + finalizer_perm=$(make_rbac_rule "olm.operatorframework.io" "clusterextensions/finalizers" '"update"' "$EXTENSION_NAME") + aggregate_rules "${finalizer_perm}" "cluster" +} + +collect_crd_rbac() { + debug "Collecting CRD permissions" + local csv="${1}" + crds=() + while IFS=$'\n' read -r crd; do + crds+=("$crd") + done < <(yq eval -o=json -r '.spec.customresourcedefinitions.owned[]?.name' "$csv") + add_rbac_rules "apiextensions.k8s.io" "customresourcedefinitions" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${crds[@]}" +} + +collect_cluster_role_rbac() { + local manifest_dir="${1}" + local csv="${2}" + debug "Adding ClusterRole RBAC" + + # Collect shipped ClusterRole names + # And the OLMv1 generated ClusterRole names + read -ra manifest_cluster_role_names <<< "$(collect_resource_names "${manifest_dir}" "ClusterRole")" + read -ra generated_cluster_role_names <<< "$(generated_cluster_role_names "${csv}")" + all_cluster_role_names=("${manifest_cluster_role_names[@]}" "${generated_cluster_role_names[@]}") + add_rbac_rules "rbac.authorization.k8s.io" "clusterroles" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_cluster_role_names[@]}" + + # Add all rules for defined in shipped ClusterRoles + # This allows the installer service account to grant rbac + manifest_cr_perms="$(collect_manifest_cluster_role_perms "${manifest_dir}")" + for item in $(echo "$manifest_cr_perms" | jq -c -r '.[]'); do + aggregate_rules "${item}" "cluster" + done + + debug "Adding ClusterPermissions" + # Add all cluster scoped rules for defined in the CSV + # This allows the installer service account to grant rbac + cluster_permissions=$(yq eval -o=json '.spec.install.spec.clusterPermissions[].rules?' "$csv" | jq -c '.[]') + for perm in ${cluster_permissions}; do + aggregate_rules "${perm}" "cluster" + done + + # Collect RBAC for cluster scoped manifest objects + collect_cluster_scoped_resource_rbac "${manifest_dir}" + + debug "Adding ClusterRoleBinding RBAC" + # Collect shipped ClusterRoleBinding names + # And the OLMv1 generated ClusterRoleBinding names (same as the generated ClusterRole names) + read -ra manifest_cluster_role_binding_names <<< "$(collect_resource_names "${manifest_dir}" "ClusterRoleBinding")" + all_cluster_role_binding_names=("${manifest_cluster_role_binding_names[@]}" "${generated_cluster_role_names[@]}") + add_rbac_rules "rbac.authorization.k8s.io" "clusterrolebindings" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_cluster_role_binding_names[@]}" +} + +collect_cluster_scoped_resource_rbac() { + debug "Adding other ClusterScoped Resources" + local manifest_dir="${1}" + for kind in "${CLUSTER_SCOPED_RESOURCES[@]}"; do + read -ra resource_names <<< "$(collect_resource_names "${manifest_dir}" "${kind}")" + if [ ${#resource_names[@]} -eq 0 ]; then + continue + fi + api_group=$(get_api_group "${manifest_dir}" "${kind}" "${resource_names[1]}") + add_rbac_rules "${api_group}" "${kind,,}s" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${resource_names[@]}" + done +} + +collect_operator_deployment_rbac() { + local manifest_dir="${1}" + local csv="${2}" + + debug "Adding Deployment RBAC" + read -ra manifest_dep_names <<< "$(collect_resource_names "${manifest_dir}" "Deployment")" + read -ra csv_deployments <<< "$(yq eval -o=json -r '.spec.install.spec.deployments[]?.name' "$csv")" + all_deployments=("${manifest_dep_names[@]}" "${csv_deployments[@]}") + add_rbac_rules "apps" "deployments" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_deployments[@]}" +} + +collect_service_account_rbac() { + debug "Adding ServiceAccount RBAC" + local manifest_dir="${1}" + local csv="${2}" + read -ra manifest_sas <<< "$(collect_resource_names "${manifest_dir}" "ServiceAccount")" + read -ra csv_sas <<< "$(yq eval '.. | select(has("serviceAccountName")) | .serviceAccountName' "${csv}" | sort -u)" + all_sas=("${manifest_sas[@]}" "${csv_sas[@]}") + add_rbac_rules "" "serviceaccounts" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_sas[@]}" +} + +collect_role_rbac() { + debug "Collecting Role RBAC" + local manifest_dir="${1}" + local csv="${2}" + + # Shipped Role manifest permissions + manifest_role_perms="$(collect_manifest_role_perms "${manifest_dir}")" + for item in $(echo "$manifest_role_perms" | jq -c -r '.[]'); do + aggregate_rules "${item}" "namespace" + done + + # CSV namespaced permissions + namespace_permissions=$(yq eval -o=json '.spec.install.spec.permissions[].rules?' "$csv" | jq -c '.[]') + for perm in ${namespace_permissions}; do + aggregate_rules "${perm}" "cluster" + done + + # Account for all other shipped namespace scoped resources + for kind in "${NAMESPACE_SCOPED_RESOURCES[@]}"; do + read -ra resource_names <<< "$(collect_resource_names "${manifest_dir}" "${kind}")" + if [ ${#resource_names[@]} -eq 0 ]; then + continue + fi + api_group=$(get_api_group "${manifest_dir}" "${kind}" "${resource_names[@]}") + add_rbac_rules "${api_group}" "${kind,,}s" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${resource_names[@]}" + done +} + +# Expects a supported bundle +collect_installer_rbac() { + local manifest_dir="${1}" + csv="$(find_csv "${manifest_dir}")" + + echo "Collecting RBAC from bundle (manifest_dir:${manifest_dir} csv: ${csv})" >&2 + + # Ensure bundle is supported by olmv1 + assert-bundle-supported "${manifest_dir}" + + # Add the required permissions rules + add_required_rules + + # Grab CSV name + collect_crd_rbac "${csv}" + collect_cluster_role_rbac "${manifest_dir}" "${csv}" + collect_operator_deployment_rbac "${manifest_dir}" "${csv}" + collect_service_account_rbac "${manifest_dir}" "${csv}" + collect_role_rbac "${manifest_dir}" "${csv}" +} diff --git a/hack/tools/catalogs/lib/hash.sh b/hack/tools/catalogs/lib/hash.sh new file mode 100644 index 0000000000..deccb7e8d5 --- /dev/null +++ b/hack/tools/catalogs/lib/hash.sh @@ -0,0 +1,63 @@ +# Library of functions that reproduce hashing behavior in internal/rukpak/convert/registryv1.go:generate_name() +# This is used to generate unique names for the ClusterRoles generated by O-C during bundle installation + +base36encode() { + if [[ -z "$1" || ! "$1" =~ ^[0-9a-fA-F]+$ ]]; then + echo "Invalid input. Please provide a hexadecimal number." + return 1 + fi + + # Convert hexadecimal to decimal + local bigint + bigint=$(echo "ibase=16; $1" | bc) + + # Convert decimal to base 36 + local base36="" + while [ "$(echo "$bigint > 0" | bc)" -eq 1 ]; do + remainder=$(echo "$bigint % 36" | bc) + bigint=$(echo "$bigint / 36" | bc) + + if [ "$remainder" -lt 10 ]; then + base36="${remainder}${base36}" + else + # Convert remainder (10-35) to corresponding ASCII letter ('a'-'z') + val=$((remainder + 87)) + char=$(echo "$val" | awk '{printf "%c", $1}') + base36="${char}${base36}" + fi + done + echo "$base36" +} + +deep_hash_object() { + local obj="$1" + + # Compute SHA-224 hash and convert to uppercase for consistency + local hash_hex + hash_hex=$(echo "$obj" | sha224sum | awk '{print toupper($1)}') + + # Encode the hash to base36 + local base36_hash + base36_hash=$(base36encode "${hash_hex}") + echo "${base36_hash}" +} + +# Function to generate a name based on the base string and hashed object +generate_name() { + local base="$1" + local obj="$2" + local max_name_length=63 # Define the maximum name length (similar to DNS limits) + + # Generate a hash from the object using deep_hash_object function + local hash_str + hash_str=$(deep_hash_object "$obj") + + # Check if the combined length exceeds the maximum length + if [ $((${#base} + ${#hash_str})) -gt $max_name_length ]; then + # Truncate the base string + base="${base:0:$((max_name_length - ${#hash_str} - 1))}" + fi + + # Return the concatenated string + echo "${base}-${hash_str}" +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/manifests.sh b/hack/tools/catalogs/lib/manifests.sh new file mode 100644 index 0000000000..156eea677e --- /dev/null +++ b/hack/tools/catalogs/lib/manifests.sh @@ -0,0 +1,121 @@ +# Library of functions for generating kube manifests + +# Function to generate the target install namespace +generate_namespace() { + cat <&2 + container_id=$($CONTAINER_RUNTIME create --quiet --entrypoint="/bin/bash" "$image") + if [ -z "$container_id" ]; then + echo "Failed to create container from image '$image'." >&2 + exit 1 + fi + + echo "${container_id}" +} + +unpack() { + local container_id="${1}" + local output_dir="${2}" + + # Extract the directory from the "operators.operatorframework.io.bundle.manifests.v1" label + local manifest_dir + echo "Unpacking bundle to ${output_dir}" >&2 + manifest_dir=$($CONTAINER_RUNTIME inspect --format '{{ index .Config.Labels "operators.operatorframework.io.bundle.manifests.v1" }}' "$container_id") + + if [ -z "$manifest_dir" ]; then + echo "No manifest directory label found on the image." + $CONTAINER_RUNTIME rm "$container_id" + exit 1 + fi + + # Copy files from the container to a temporary directory + $CONTAINER_RUNTIME cp "$container_id:$manifest_dir/." "$output_dir" > /dev/null + + # Clean up the container + $CONTAINER_RUNTIME rm "$container_id" > /dev/null +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/utils.sh b/hack/tools/catalogs/lib/utils.sh new file mode 100644 index 0000000000..bebbf27cba --- /dev/null +++ b/hack/tools/catalogs/lib/utils.sh @@ -0,0 +1,49 @@ +# Library of utility functions + +debug() { + if [ "${DEBUG,,}" != "false" ] && [ -n "$DEBUG" ]; then + echo "DEBUG: $1" >&2 + fi +} + +print-banner() { + local red='\033[91m' + local white='\033[97m' + local reset='\033[0m' + local green='\033[92m' + + echo -e "${white}===========================================================================================================================${reset}" + echo -e "${white}‖${red} ____ __ ______ __ ${white}‖${reset}" + echo -e "${white}‖${red} / __ \ ____ ___ _____ ____ _ / /_ ____ _____ / ____/_____ ____ _ ____ ___ ___ _ __ ____ _____ / /__ ${white}‖${reset}" + echo -e "${white}‖${red} / / / // __ \ / _ \ / ___// __ \`// __// __ \ / ___/ / /_ / ___// __ \`// __ \`__ \ / _ \| | /| / // __ \ / ___// //_/ ${white}‖${reset}" + echo -e "${white}‖${red} / /_/ // /_/ // __// / / /_/ // /_ / /_/ // / / __/ / / / /_/ // / / / / // __/| |/ |/ // /_/ // / / ,< ${white}‖${reset}" + echo -e "${white}‖${red} \____// .___/ \___//_/ \__,_/ \__/ \____//_/ /_/ /_/ \__,_//_/ /_/ /_/ \___/ |__/|__/ \____//_/ /_/|_| ${white}‖${reset}" + echo -e "${white}‖${red} /_/${green} OLM v1 ${white}‖${reset}" + echo -e "${white}===========================================================================================================================${reset}" +} + +assert-commands() { + for cmd in "$@"; do + if ! command -v "$cmd" &>/dev/null; then + echo "Required command '$cmd' not found in PATH" >&2 + exit 1 + fi + done +} + +assert-container-runtime() { + if [ -z "$CONTAINER_RUNTIME" ]; then + if command -v podman &>/dev/null; then + export CONTAINER_RUNTIME="podman" + elif command -v docker &>/dev/null; then + export CONTAINER_RUNTIME="docker" + else + echo "No container runtime found in PATH. If not using docker or podman, please set the CONTAINER_RUNTIME environment variable to your container runtime" >&2 + exit 1 + fi + fi + if ! command -v "$CONTAINER_RUNTIME" &>/dev/null; then + echo "Configured container runtime '$CONTAINER_RUNTIME' not found in PATH" >&2 + exit 1 + fi +} \ No newline at end of file diff --git a/hack/tools/catalogs/list-compatible-bundles b/hack/tools/catalogs/list-compatible-bundles new file mode 100755 index 0000000000..001dd7135d --- /dev/null +++ b/hack/tools/catalogs/list-compatible-bundles @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check required tools are installed +assert-commands jq + +usage() { + print-banner + echo "" + echo "Usage: $0 [-r ] < " + echo "" + echo "Filter Catalog FBC in stdin for OLMv1 (potentially) supported bundles" + echo "" + echo "Examples:" + echo " # Filter for all OLMv1 (potentially) supported bundles" + echo " $0 < operatorhubio-catalog.json" + echo "" + echo " # Filter for all OLMv1 (potentially) supported bundles that contain argocd in the package name" + echo " $0 -r argocd < operatorhubio-catalog.json" + echo "" + echo "NOTE: OLM v1 currently only supports bundles that fulfill the following criteria: " + echo " - Support AllNamespaces install model" + echo " - Don't have dependencies" + echo " - Don't contain webhooks" + echo " - Don't interact with the OperatorConditions API (only verifiable at runtime)" + echo "WARNING: These results may include bundles with webhooks, which are incompatible" +} + +# Parse the optional regex argument +REGEX="" +while getopts "r:" opt; do + case ${opt} in + r ) + REGEX=$OPTARG + ;; + \? ) + usage + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + +# Select bundle documents +select-bundle-documents() { + jq 'select(.schema == "olm.bundle")' +} + +# Select bundles that declare AllNamespace install mode +# or declare nothing at all (older released bundles sans "olm.csv.metadata" property) +that-support-allnamespace-install-mode() { + jq 'select( + all(.properties[].type; . != "olm.csv.metadata") or + (.properties[]? | + select(.type == "olm.csv.metadata" and + (.value.installModes[]? | + select(.type == "AllNamespaces" and .supported == true) + ) + ) + ) + )' +} + +# Select bundles without dependencies +that-dont-have-dependencies() { + jq 'select(all(.properties[].type; . != "olm.package.required" and . != "olm.gvk.required"))' +} + +# Select the "olm.package" property from the bundles +# This contains the packageName and version information +extract-olm-package-property() { + jq '.properties[] | select(.type == "olm.package") |.value' +} + +# Group packages by name and collect versions into an array +group-versions-by-package-name() { + jq -s 'group_by(.packageName) | map({packageName: .[0].packageName, versions: map(.version)})' +} + +# Apply regex on name +filter-by-regex-if-necessary() { + jq --arg regex "$REGEX" ' + if $regex != "" then + map(select(.packageName | test($regex))) + else + . + end' +} + +cat - | select-bundle-documents | that-support-allnamespace-install-mode | that-dont-have-dependencies | extract-olm-package-property | group-versions-by-package-name | filter-by-regex-if-necessary + +echo "NOTE: OLM v1 currently only supports bundles that support AllNamespaces install model, don't have dependencies, and don't contain webhooks" >&2 +echo "WARNING: These results may include bundles with webhooks, which are incompatible" >&2 diff --git a/hack/tools/catalogs/unpack-bundle b/hack/tools/catalogs/unpack-bundle new file mode 100755 index 0000000000..684c90d48f --- /dev/null +++ b/hack/tools/catalogs/unpack-bundle @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") + +source "${SCRIPT_ROOT}/lib/unpack.sh" +source "${SCRIPT_ROOT}/lib/utils.sh" + +# Check there is a container runtime (podman, or docker) +# If neither are found, check CONTAINER_RUNTIME is set and exists in PATH +assert-container-runtime + +usage() { + print-banner + echo "" + echo "Usage: $0 [-o output_directory] bundle_image" + echo "" + echo "Unpack a bundle image to a directory``" + echo "" + echo "Examples:" + echo " # Unpack argcocd-operator bundle image to a temporary directory" + echo " $0 quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3" + echo "" + echo " # Unpack the bundle image to a specific directory" + echo " $0 -o argocd-manifests quay.io/operatorhubio/argocd-operator@sha256:d538c45a813b38ef0e44f40d279dc2653f97ca901fb660da5d7fe499d51ad3b3" +} + +# Initialize variables +output_directory=$(mktemp -d) +bundle_image="" + +# Parse command line arguments +while getopts "o:" opt; do + case ${opt} in + o) + output_directory=${OPTARG} + ;; + *) + usage + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + +# Check if bundle image argument is provided or read from stdin +if [ -z "$1" ]; then + if [ -t 0 ]; then + echo "Error: Docker image name is required." + usage + else + read -r bundle_image + fi +else + bundle_image="$1" +fi + +if [ -z "${bundle_image}" ]; then + echo "Error: Docker image name is required." + usage + exit 1 +fi + +# Validate output directory +if [ ! -d "$output_directory" ]; then + mkdir -p "$output_directory" +fi + +# Unpack the bundle image +container_id="$(create_container "$bundle_image")" +unpack "${container_id}" "${output_directory}" + +echo "${output_directory}" \ No newline at end of file diff --git a/hack/tools/check-go-version.sh b/hack/tools/check-go-version.sh new file mode 100755 index 0000000000..59eb8b9711 --- /dev/null +++ b/hack/tools/check-go-version.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +U_FLAG='false' +B_FLAG='' + +usage() { + cat <] [-h] [-u] + +Reports on golang mod file version updates, returns an error when a go.mod +file exceeds the root go.mod file (used as a threshold). + +Options: + -b git reference (branch or SHA) to use as a baseline. + Defaults to 'main'. + -h Help (this text). + -u Error on any update, even below the threshold. +EOF +} + +while getopts 'b:hu' f; do + case "${f}" in + b) B_FLAG="${OPTARG}" ;; + h) usage + exit 0 ;; + u) U_FLAG='true' ;; + *) echo "Unknown flag ${f}" + usage + exit 1 ;; + esac +done + +BASE_REF=${B_FLAG:-main} +ROOT_GO_MOD="./go.mod" +GO_VER=$(sed -En 's/^go (.*)$/\1/p' "${ROOT_GO_MOD}") +OLDIFS="${IFS}" +IFS='.' MAX_VER=(${GO_VER}) +IFS="${OLDIFS}" + +if [ ${#MAX_VER[*]} -ne 3 -a ${#MAX_VER[*]} -ne 2 ]; then + echo "Invalid go version: ${GO_VER}" + exit 1 +fi + +GO_MAJOR=${MAX_VER[0]} +GO_MINOR=${MAX_VER[1]} +GO_PATCH=${MAX_VER[2]} +OVERRIDE_LABEL="override-go-verdiff" + +RETCODE=0 + +check_version () { + local whole=$1 + local file=$2 + OLDIFS="${IFS}" + IFS='.' ver=(${whole}) + IFS="${OLDIFS}" + + if [ ${ver[0]} -gt ${GO_MAJOR} ]; then + echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" + return 1 + fi + if [ ${ver[1]} -gt ${GO_MINOR} ]; then + echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" + return 1 + fi + + if [ ${#ver[*]} -eq 2 ] ; then + return 0 + fi + if [ ${#ver[*]} -ne 3 ] ; then + echo "${file}: ${whole}: Badly formatted golang version" + return 1 + fi + + if [ ${ver[1]} -eq ${GO_MINOR} -a ${ver[2]} -gt ${GO_PATCH} ]; then + echo "${file}: ${whole}: Bad golang version (expected ${GO_VER} or less)" + return 1 + fi + return 0 +} + +echo "Found golang version: ${GO_VER}" + +for f in $(find . -name "*.mod"); do + v=$(sed -En 's/^go (.*)$/\1/p' ${f}) + if [ -z ${v} ]; then + echo "${f}: Skipping, no version found" + continue + fi + if ! check_version ${v} ${f}; then + RETCODE=1 + fi + old=$(git grep -ohP '^go .*$' "${BASE_REF}" -- "${f}") + old=${old#go } + new=$(git grep -ohP '^go .*$' "${f}") + new=${new#go } + # If ${old} is empty, it means this is a new .mod file + if [ -z "${old}" ]; then + continue + fi + # Check if patch version remains 0: X.x.0 <-> X.x + if [ "${new}.0" == "${old}" -o "${new}" == "${old}.0" ]; then + continue + fi + if [ "${new}" != "${old}" ]; then + # We NEED to report on changes in the root go.mod, regardless of the U_FLAG + if [ "${f}" == "${ROOT_GO_MOD}" ]; then + echo "${f}: ${v}: Updated ROOT golang version from ${old}" + RETCODE=1 + continue + fi + if ${U_FLAG}; then + echo "${f}: ${v}: Updated golang version from ${old}" + RETCODE=1 + fi + fi +done + +for l in ${LABELS}; do + if [ "$l" == "${OVERRIDE_LABEL}" ]; then + if [ ${RETCODE} -eq 1 ]; then + echo "" + echo "Found ${OVERRIDE_LABEL} label, overriding failed results." + RETCODE=0 + fi + fi +done + +if [ ${RETCODE} -eq 1 ]; then + echo "" + echo "This test result may be overridden by applying the (${OVERRIDE_LABEL}) label to this PR and re-running the CI job." +fi + +exit ${RETCODE} diff --git a/hack/tools/crd-generator/README.md b/hack/tools/crd-generator/README.md new file mode 100644 index 0000000000..4334721672 --- /dev/null +++ b/hack/tools/crd-generator/README.md @@ -0,0 +1,53 @@ +# Guide to Operator-Controller CRD extensions + +All operator-controller (`opcon` for short) extensions to CRDs are part of the +comments to fields within the APIs. The fields look like XML tags, to distinguish +them from kubebuilder tags. + +All tags start with `` and have additional fields in between. +Usually the second field is `experimental`. Some tags may have an end tag (like XML) +that starts with `` + +The field that follows is experimental, and is not included in the standard CRD. It *is* included +in the experimental CRD. + +## Experimental Validation + +* Tag: `` +* Tag: `` + +A standard and/or experimental validation which may differ from one another. For example, where the +experimental CRD has extra enumerations. + +Where `VALIDATION` is one of: + +* `Enum=list;of;enums` + +A semi-colon separated list of enumerations, similar to the `+kubebuilder:validation:Enum` scheme. + +* `XValidation:message="something",rule="something"` + +An XValidation scheme, similar to the `+kubebuilder:validation:XValidation` scheme, but more limited. + +## Experimental Description + +* Start Tag: `` +* End Tag: `` + +Descriptive text that is only included as part of the field description within the experimental CRD. +All text between the tags is included in the experimental CRD, but removed from the standard CRD. + +This is only useful if the field is included in the standard CRD, but there's additional meaning in +the experimental CRD when feature gates are enabled. + +## Exclude from CRD Description + +* Start Tag: `` +* End Tag: `` + +Descriptive text that is excluded from the CRD description. This is similar to the use of `---`, except +the three hypens excludes *all* following text. diff --git a/hack/tools/crd-generator/main.go b/hack/tools/crd-generator/main.go new file mode 100644 index 0000000000..9687489f45 --- /dev/null +++ b/hack/tools/crd-generator/main.go @@ -0,0 +1,318 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "regexp" + "strings" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-tools/pkg/crd" + "sigs.k8s.io/controller-tools/pkg/loader" + "sigs.k8s.io/controller-tools/pkg/markers" + "sigs.k8s.io/yaml" +) + +const ( + // FeatureSetAnnotation is the annotation key used in the Operator-Controller API CRDs to specify + // the installed Operator-Controller API channel. + GeneratorAnnotation = "olm.operatorframework.io/generator" + VersionAnnotation = "controller-gen.kubebuilder.io/version" + StandardChannel = "standard" + ExperimentalChannel = "experimental" +) + +var standardKinds = map[string]bool{ + "ClusterExtension": true, + "ClusterCatalog": true, +} + +// This generation code is largely copied from below into operator-controller +// github.com/kubernetes-sigs/gateway-api/blob/b7d2c5788bf38fc2c18085de524e204034c69a14/pkg/generator/main.go +// This generation code is largely copied from below into gateway-api +// github.com/kubernetes-sigs/controller-tools/blob/ab52f76cc7d167925b2d5942f24bf22e30f49a02/pkg/crd/gen.go +func main() { + runGenerator(os.Args[1:]...) +} + +func runGenerator(args ...string) { + outputDir := "config/crd" + ctVer := "" + crdRoot := "github.com/operator-framework/operator-controller/api/v1" + if len(args) >= 1 { + // Get the output directory + outputDir = args[0] + } + if len(args) >= 2 { + // get the controller-tools version + ctVer = args[1] + } + if len(args) >= 3 { + crdRoot = args[2] + } + + roots, err := loader.LoadRoots( + "k8s.io/apimachinery/pkg/runtime/schema", // Needed to parse generated register functions. + crdRoot, + ) + if err != nil { + log.Fatalf("failed to load package roots: %s", err) + } + + generator := &crd.Generator{} + + parser := &crd.Parser{ + Collector: &markers.Collector{Registry: &markers.Registry{}}, + Checker: &loader.TypeChecker{ + NodeFilters: []loader.NodeFilter{generator.CheckFilter()}, + }, + } + + err = generator.RegisterMarkers(parser.Collector.Registry) + if err != nil { + log.Fatalf("failed to register markers: %s", err) + } + + crd.AddKnownTypes(parser) + for _, r := range roots { + parser.NeedPackage(r) + } + + metav1Pkg := crd.FindMetav1(roots) + if metav1Pkg == nil { + log.Fatalf("no objects in the roots, since nothing imported metav1") + } + + kubeKinds := crd.FindKubeKinds(parser, metav1Pkg) + if len(kubeKinds) == 0 { + log.Fatalf("no objects in the roots") + } + + channels := []string{StandardChannel, ExperimentalChannel} + for _, channel := range channels { + for _, groupKind := range kubeKinds { + if channel == StandardChannel && !standardKinds[groupKind.Kind] { + continue + } + + log.Printf("generating %s CRD for %v\n", channel, groupKind) + + parser.NeedCRDFor(groupKind, nil) + crdRaw := parser.CustomResourceDefinitions[groupKind] + + // Inline version of "addAttribution(&crdRaw)" ... + if crdRaw.Annotations == nil { + crdRaw.Annotations = map[string]string{} + } + crdRaw.Annotations[GeneratorAnnotation] = channel + if ctVer != "" { + crdRaw.Annotations[VersionAnnotation] = ctVer + } + + // Prevent the top level metadata for the CRD to be generated regardless of the intention in the arguments + crd.FixTopLevelMetadata(crdRaw) + + channelCrd := crdRaw.DeepCopy() + for i, version := range channelCrd.Spec.Versions { + if channel == StandardChannel && strings.Contains(version.Name, "alpha") { + channelCrd.Spec.Versions[i].Served = false + } + version.Schema.OpenAPIV3Schema.Properties = opconTweaksMap(channel, version.Schema.OpenAPIV3Schema.Properties) + } + + conv, err := crd.AsVersion(*channelCrd, apiextensionsv1.SchemeGroupVersion) + if err != nil { + log.Fatalf("failed to convert CRD: %s", err) + } + + out, err := yaml.Marshal(conv) + if err != nil { + log.Fatalf("failed to marshal CRD: %s", err) + } + + // Do some filtering of the resulting YAML + var yamlData map[string]any + err = yaml.Unmarshal(out, &yamlData) + if err != nil { + log.Fatalf("failed to unmarshal data: %s", err) + } + + scrapYaml(yamlData, "status") + scrapYaml(yamlData, "metadata", "creationTimestamp") + + out, err = yaml.Marshal(yamlData) + if err != nil { + log.Fatalf("failed to re-marshal CRD: %s", err) + } + + // If missing, add a break at the beginning of the file + breakLine := []byte("---\n") + if !bytes.HasPrefix(out, breakLine) { + out = append(breakLine, out...) + } + + fileName := fmt.Sprintf("%s/%s/%s_%s.yaml", outputDir, channel, crdRaw.Spec.Group, crdRaw.Spec.Names.Plural) + err = os.WriteFile(fileName, out, 0o600) + if err != nil { + log.Fatalf("failed to write CRD: %s", err) + } + } + } +} + +func opconTweaksMap(channel string, props map[string]apiextensionsv1.JSONSchemaProps) map[string]apiextensionsv1.JSONSchemaProps { + for name := range props { + jsonProps := props[name] + p := opconTweaks(channel, name, jsonProps) + if p == nil { + delete(props, name) + } else { + props[name] = *p + } + } + return props +} + +// Custom Opcon API Tweaks for tags prefixed with `") { + return nil + } + } + + // TODO(robscott): Figure out why crdgen switched this to "object" + if jsonProps.Format == "date-time" { + jsonProps.Type = "string" + } + + validationPrefix := fmt.Sprintf(" 0 { + enumRe := regexp.MustCompile(validationPrefix + "Enum=([A-Za-z;]*)>") + enumMatches := enumRe.FindAllStringSubmatch(jsonProps.Description, 64) + for _, enumMatch := range enumMatches { + if len(enumMatch) != 2 { + log.Fatalf("Invalid %s Enum tag for %s", validationPrefix, name) + } + + numValid++ + jsonProps.Enum = []apiextensionsv1.JSON{} + for _, val := range strings.Split(enumMatch[1], ";") { + jsonProps.Enum = append(jsonProps.Enum, apiextensionsv1.JSON{Raw: []byte("\"" + val + "\"")}) + } + } + + celRe := regexp.MustCompile(validationPrefix + "XValidation:rule=\"([^\"]*)\",message=\"([^\"]*)\">") + celMatches := celRe.FindAllStringSubmatch(jsonProps.Description, 64) + for _, celMatch := range celMatches { + if len(celMatch) != 3 { + log.Fatalf("Invalid %s CEL tag for %s", validationPrefix, name) + } + + numValid++ + jsonProps.XValidations = append(jsonProps.XValidations, apiextensionsv1.ValidationRule{ + Message: celMatch[1], + Rule: celMatch[2], + }) + } + } + + if numValid < numExpressions { + log.Fatalf("Found %d Opcon validation expressions, but only %d were valid", numExpressions, numValid) + } + + jsonProps.Description = formatDescription(jsonProps.Description, channel, name) + + if len(jsonProps.Properties) > 0 { + jsonProps.Properties = opconTweaksMap(channel, jsonProps.Properties) + } else if jsonProps.Items != nil && jsonProps.Items.Schema != nil { + jsonProps.Items.Schema = opconTweaks(channel, name, *jsonProps.Items.Schema) + } + + return &jsonProps +} + +func formatDescription(description string, channel string, name string) string { + startTag := "" + endTag := "" + if channel == StandardChannel && strings.Contains(description, startTag) { + regexPattern := `\n*` + regexp.QuoteMeta(startTag) + `(?s:(.*?))` + regexp.QuoteMeta(endTag) + `\n*` + re := regexp.MustCompile(regexPattern) + match := re.FindStringSubmatch(description) + if len(match) != 2 { + log.Fatalf("Invalid tag for %s", name) + } + description = re.ReplaceAllString(description, "\n\n") + } else { + description = strings.ReplaceAll(description, startTag, "") + description = strings.ReplaceAll(description, endTag, "") + } + + // Comments within "opcon:util:excludeFromCRD" tag are not included in the generated CRD and all trailing \n operators before + // and after the tags are removed and replaced with three \n operators. + startTag = "" + endTag = "" + if strings.Contains(description, startTag) { + regexPattern := `\n*` + regexp.QuoteMeta(startTag) + `(?s:(.*?))` + regexp.QuoteMeta(endTag) + `\n*` + re := regexp.MustCompile(regexPattern) + match := re.FindStringSubmatch(description) + if len(match) != 2 { + log.Fatalf("Invalid tag for %s", name) + } + description = re.ReplaceAllString(description, "\n\n\n") + } + + opconRe := regexp.MustCompile(``) + description = opconRe.ReplaceAllLiteralString(description, "") + + // Remove anything following three hyphens + regexPattern := `(?s)---.*` + re := regexp.MustCompile(regexPattern) + description = re.ReplaceAllString(description, "") + + // Remove any extra \n (more than 2 and all trailing at the end) + regexPattern = `\n\n+` + re = regexp.MustCompile(regexPattern) + description = re.ReplaceAllString(description, "\n\n") + description = strings.Trim(description, "\n") + + return description +} + +// delete a field in unstructured YAML +func scrapYaml(data map[string]any, fields ...string) { + if len(fields) == 0 { + return + } + if len(fields) == 1 { + delete(data, fields[0]) + return + } + if f, ok := data[fields[0]]; ok { + if f2, ok := f.(map[string]any); ok { + scrapYaml(f2, fields[1:]...) + } + } +} diff --git a/hack/tools/crd-generator/main_test.go b/hack/tools/crd-generator/main_test.go new file mode 100644 index 0000000000..d2eb28d61f --- /dev/null +++ b/hack/tools/crd-generator/main_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +const controllerToolsVersion = "v0.19.0" + +func TestRunGenerator(t *testing.T) { + here, err := os.Getwd() + require.NoError(t, err) + // Get to repo root + err = os.Chdir("../../..") + require.NoError(t, err) + defer func() { + _ = os.Chdir(here) + }() + dir, err := os.MkdirTemp("", "crd-generate-*") + require.NoError(t, err) + defer os.RemoveAll(dir) + require.NoError(t, os.Mkdir(filepath.Join(dir, "standard"), 0o700)) + require.NoError(t, os.Mkdir(filepath.Join(dir, "experimental"), 0o700)) + runGenerator(dir, controllerToolsVersion) + + f1 := filepath.Join(dir, "standard/olm.operatorframework.io_clusterextensions.yaml") + f2 := "helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) + + f1 = filepath.Join(dir, "standard/olm.operatorframework.io_clustercatalogs.yaml") + f2 = "helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) + + f1 = filepath.Join(dir, "experimental/olm.operatorframework.io_clusterextensions.yaml") + f2 = "helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) + + f1 = filepath.Join(dir, "experimental/olm.operatorframework.io_clustercatalogs.yaml") + f2 = "helm/olmv1/base/catalogd/crd/experimental/olm.operatorframework.io_clustercatalogs.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) +} + +func TestTags(t *testing.T) { + here, err := os.Getwd() + require.NoError(t, err) + err = os.Chdir("testdata") + defer func() { + _ = os.Chdir(here) + }() + require.NoError(t, err) + dir, err := os.MkdirTemp("", "crd-generate-*") + require.NoError(t, err) + defer os.RemoveAll(dir) + require.NoError(t, os.Mkdir(filepath.Join(dir, "standard"), 0o700)) + require.NoError(t, os.Mkdir(filepath.Join(dir, "experimental"), 0o700)) + runGenerator(dir, controllerToolsVersion, "github.com/operator-framework/operator-controller/hack/tools/crd-generator/testdata/api/v1") + + f1 := filepath.Join(dir, "standard/olm.operatorframework.io_clusterextensions.yaml") + f2 := "output/standard/olm.operatorframework.io_clusterextensions.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) + + f1 = filepath.Join(dir, "experimental/olm.operatorframework.io_clusterextensions.yaml") + f2 = "output/experimental/olm.operatorframework.io_clusterextensions.yaml" + fmt.Printf("comparing: %s to %s\n", f1, f2) + compareFiles(t, f1, f2) +} + +func compareFiles(t *testing.T, file1, file2 string) { + f1, err := os.Open(file1) + require.NoError(t, err) + defer func() { + _ = f1.Close() + }() + + f2, err := os.Open(file2) + require.NoError(t, err) + defer func() { + _ = f2.Close() + }() + + for { + b1 := make([]byte, 64000) + b2 := make([]byte, 64000) + n1, err1 := f1.Read(b1) + n2, err2 := f2.Read(b2) + + // Success if both have EOF at the same time + if err1 == io.EOF && err2 == io.EOF { + return + } + require.NoError(t, err1) + require.NoError(t, err2) + require.Equal(t, n1, n2) + require.Equal(t, b1, b2) + } +} diff --git a/api/v1alpha1/clusterextension_types.go b/hack/tools/crd-generator/testdata/api/v1/clusterextension_types.go similarity index 50% rename from api/v1alpha1/clusterextension_types.go rename to hack/tools/crd-generator/testdata/api/v1/clusterextension_types.go index 433f37859a..8e134a3429 100644 --- a/api/v1alpha1/clusterextension_types.go +++ b/hack/tools/crd-generator/testdata/api/v1/clusterextension_types.go @@ -14,20 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha1 +package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/operator-framework/operator-controller/internal/conditionsets" ) -var ( - ClusterExtensionKind = "ClusterExtension" -) +var ClusterExtensionKind = "ClusterExtension" -type UpgradeConstraintPolicy string -type CRDUpgradeSafetyPolicy string +type ( + UpgradeConstraintPolicy string + CRDUpgradeSafetyEnforcement string +) const ( // The extension will only upgrade if the new version satisfies @@ -45,6 +43,35 @@ const ( // ClusterExtensionSpec defines the desired state of ClusterExtension type ClusterExtensionSpec struct { + // namespace is a reference to a Kubernetes namespace. + // This is the namespace in which the provided ServiceAccount must exist. + // It also designates the default namespace where namespace-scoped resources + // for the extension are applied to the cluster. + // Some extensions may contain namespace-scoped resources to be applied in other namespaces. + // This namespace must exist. + // + // namespace is required, immutable, and follows the DNS label standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + // start and end with an alphanumeric character, and be no longer than 63 characters + // + // [RFC 1123]: https://tools.ietf.org/html/rfc1123 + // + // +kubebuilder:validation:MaxLength:=63 + // + // + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label" + // +kubebuilder:validation:Required + Namespace string `json:"namespace"` + + // serviceAccount is a reference to a ServiceAccount used to perform all interactions + // with the cluster that are required to manage the extension. + // The ServiceAccount must be configured with the necessary permissions to perform these interactions. + // The ServiceAccount must exist in the namespace referenced in the spec. + // serviceAccount is required. + // + // +kubebuilder:validation:Required + ServiceAccount ServiceAccountReference `json:"serviceAccount"` + // source is a required field which selects the installation source of content // for this ClusterExtension. Selection is performed by setting the sourceType. // @@ -58,111 +85,82 @@ type ClusterExtensionSpec struct { // catalog: // packageName: example-package // + // +kubebuilder:validation:Required Source SourceConfig `json:"source"` - // install is a required field used to configure the installation options - // for the ClusterExtension such as the installation namespace, - // the service account and the pre-flight check configuration. + // install is an optional field used to configure the installation options + // for the ClusterExtension such as the pre-flight check configuration. // - // Below is a minimal example of an installation definition (in yaml): - // install: - // namespace: example-namespace - // serviceAccount: - // name: example-sa - Install ClusterExtensionInstallConfig `json:"install"` + // +optional + Install *ClusterExtensionInstallConfig `json:"install,omitempty"` } const SourceTypeCatalog = "Catalog" // SourceConfig is a discriminated union which selects the installation source. +// // +union -// +kubebuilder:validation:XValidation:rule="self.sourceType == 'Catalog' && has(self.catalog)",message="sourceType Catalog requires catalog field" +// +kubebuilder:validation:XValidation:rule="has(self.sourceType) && self.sourceType == 'Catalog' ? has(self.catalog) : !has(self.catalog)",message="catalog is required when sourceType is Catalog, and forbidden otherwise" type SourceConfig struct { // sourceType is a required reference to the type of install source. // - // Allowed values are ["Catalog"] + // Allowed values are "Catalog" // - // When this field is set to "Catalog", information for determining the appropriate - // bundle of content to install will be fetched from ClusterCatalog resources existing - // on the cluster. When using the Catalog sourceType, the catalog field must also be set. + // When this field is set to "Catalog", information for determining the + // appropriate bundle of content to install will be fetched from + // ClusterCatalog resources existing on the cluster. + // When using the Catalog sourceType, the catalog field must also be set. // // +unionDiscriminator - // +kubebuilder:validation:Enum:="Catalog" + // + // + // +kubebuilder:validation:Required SourceType string `json:"sourceType"` - // catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog", - // and must be the only field defined for this sourceType. + // catalog is used to configure how information is sourced from a catalog. + // This field is required when sourceType is "Catalog", and forbidden otherwise. + // + // + // This is the experimental description for Catalog + // // - // +optional. - Catalog *CatalogSource `json:"catalog,omitempty"` + // + // No one should see this! + // + // + // +optional + Catalog *CatalogFilter `json:"catalog,omitempty"` + + // test is a required parameter + // + Test string `json:"test"` } // ClusterExtensionInstallConfig is a union which selects the clusterExtension installation config. // ClusterExtensionInstallConfig requires the namespace and serviceAccount which should be used for the installation of packages. +// +// +kubebuilder:validation:XValidation:rule="has(self.preflight)",message="at least one of [preflight] are required when install is specified" // +union type ClusterExtensionInstallConfig struct { - // namespace is a reference to the Namespace in which the bundle of - // content for the package referenced in the packageName field will be applied. - // The bundle may contain cluster-scoped resources or resources that are - // applied to other Namespaces. This Namespace is expected to exist. - // - // namespace is required, immutable, and follows the DNS label standard - // as defined in [RFC 1123]. This means that valid values: - // - Contain no more than 63 characters - // - Contain only lowercase alphanumeric characters or '-' - // - Start with an alphanumeric character - // - End with an alphanumeric character - // - // Some examples of valid values are: - // - some-namespace - // - 123-namespace - // - 1-namespace-2 - // - somenamespace - // - // Some examples of invalid values are: - // - -some-namespace - // - some-namespace- - // - thisisareallylongnamespacenamethatisgreaterthanthemaximumlength - // - some.namespace - // - // [RFC 1123]: https://tools.ietf.org/html/rfc1123 - // - //+kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - //+kubebuilder:validation:MaxLength:=63 - //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable" - Namespace string `json:"namespace"` - - // serviceAccount is a required reference to a ServiceAccount that exists - // in the installNamespace. The provided ServiceAccount is used to install and - // manage the content for the package specified in the packageName field. - // - // In order to successfully install and manage the content for the package, - // the ServiceAccount provided via this field should be configured with the - // appropriate permissions to perform the necessary operations on all the - // resources that are included in the bundle of content being applied. - ServiceAccount ServiceAccountReference `json:"serviceAccount"` - - // preflight is an optional field that can be used to configure the preflight checks run before installation or upgrade of the content for the package specified in the packageName field. - // - // When specified, it overrides the default configuration of the preflight checks that are required to execute successfully during an install/upgrade operation. + // preflight is an optional field that can be used to configure the checks that are + // run before installation or upgrade of the content for the package specified in the packageName field. // - // When not specified, the default configuration for each preflight check will be used. + // When specified, it replaces the default preflight configuration for install/upgrade actions. + // When not specified, the default configuration will be used. // - //+optional + // +optional Preflight *PreflightConfig `json:"preflight,omitempty"` } -// CatalogSource defines the required fields for catalog source. -type CatalogSource struct { +// CatalogFilter defines the attributes used to identify and filter content from a catalog. +type CatalogFilter struct { // packageName is a reference to the name of the package to be installed // and is used to filter the content from catalogs. // - // This field is required, immutable and follows the DNS subdomain name - // standard as defined in [RFC 1123]. This means that valid entries: - // - Contain no more than 253 characters - // - Contain only lowercase alphanumeric characters, '-', or '.' - // - Start with an alphanumeric character - // - End with an alphanumeric character + // packageName is required, immutable, and follows the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. // // Some examples of valid values are: // - some-package @@ -178,9 +176,11 @@ type CatalogSource struct { // // [RFC 1123]: https://tools.ietf.org/html/rfc1123 // - //+kubebuilder:validation:MaxLength:=253 - //+kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="packageName is immutable" + // +kubebuilder:validation.Required + // +kubebuilder:validation:MaxLength:=253 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="packageName is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" + // +kubebuilder:validation:Required PackageName string `json:"packageName"` // version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. @@ -257,15 +257,20 @@ type CatalogSource struct { // // For more information on semver, please see https://semver.org/ // - //+kubebuilder:validation:MaxLength:=64 - //+kubebuilder:validation:Pattern=`^(\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|[x|X|\*])(\.(0|[1-9]\d*|x|X|\*]))?(\.(0|[1-9]\d*|x|X|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+|,\s*|\s*\|\|\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|x|X|\*])(\.(0|[1-9]\d*|x|X|\*))?(\.(0|[1-9]\d*|x|X|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$` - //+optional + // +kubebuilder:validation:MaxLength:=64 + // +kubebuilder:validation:XValidation:rule="self.matches(\"^(\\\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|[x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)((?:\\\\s+|,\\\\s*|\\\\s*\\\\|\\\\|\\\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)*$\")",message="invalid version expression" + // +optional Version string `json:"version,omitempty"` // channels is an optional reference to a set of channels belonging to // the package specified in the packageName field. // - // A "channel" is a package author defined stream of updates for an extension. + // A "channel" is a package-author-defined stream of updates for an extension. + // + // Each channel in the list must follow the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. No more than 256 channels can be specified. // // When specified, it is used to constrain the set of installable bundles and // the automated upgrade path. This constraint is an AND operation with the @@ -277,13 +282,6 @@ type CatalogSource struct { // // When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. // - // This field follows the DNS subdomain name standard as defined in [RFC - // 1123]. This means that valid entries: - // - Contain no more than 253 characters - // - Contain only lowercase alphanumeric characters, '-', or '.' - // - Start with an alphanumeric character - // - End with an alphanumeric character - // // Some examples of valid values are: // - 1.1.x // - alpha @@ -303,9 +301,10 @@ type CatalogSource struct { // // [RFC 1123]: https://tools.ietf.org/html/rfc1123 // - //+kubebuilder:validation:items:MaxLength:=253 - //+kubebuilder:validation:items:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - //+optional + // +kubebuilder:validation:items:MaxLength:=253 + // +kubebuilder:validation:MaxItems:=256 + // +kubebuilder:validation:items:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="channels entries must be valid DNS1123 subdomains" + // +optional Channels []string `json:"channels,omitempty"` // selector is an optional field that can be used @@ -315,14 +314,14 @@ type CatalogSource struct { // When unspecified, all ClusterCatalogs will be used in // the bundle selection process. // - //+optional - Selector metav1.LabelSelector `json:"selector,omitempty"` + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty"` // upgradeConstraintPolicy is an optional field that controls whether // the upgrade path(s) defined in the catalog are enforced for the package // referenced in the packageName field. // - // Allowed values are: ["CatalogProvided", "SelfCertified"]. + // Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. // // When this field is set to "CatalogProvided", automatic upgrades will only occur // when upgrade constraints specified by the package author are met. @@ -334,28 +333,26 @@ type CatalogSource struct { // loss. It is assumed that users have independently verified changes when // using this option. // - // If unspecified, the default value is "CatalogProvided". + // When this field is omitted, the default value is "CatalogProvided". // - //+kubebuilder:validation:Enum:=CatalogProvided;SelfCertified - //+kubebuilder:default:=CatalogProvided - //+optional + // +kubebuilder:validation:Enum:=CatalogProvided;SelfCertified + // +kubebuilder:default:=CatalogProvided + // +optional UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"` } -// ServiceAccountReference references a serviceAccount. +// ServiceAccountReference identifies the serviceAccount used fo install a ClusterExtension. type ServiceAccountReference struct { // name is a required, immutable reference to the name of the ServiceAccount // to be used for installation and management of the content for the package // specified in the packageName field. // - // This ServiceAccount is expected to exist in the installNamespace. + // This ServiceAccount must exist in the installNamespace. // - // This field follows the DNS subdomain name standard as defined in [RFC - // 1123]. This means that valid values: - // - Contain no more than 253 characters - // - Contain only lowercase alphanumeric characters, '-', or '.' - // - Start with an alphanumeric character - // - End with an alphanumeric character + // name follows the DNS subdomain standard as defined in [RFC 1123]. + // It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. // // Some examples of valid values are: // - some-serviceaccount @@ -370,13 +367,15 @@ type ServiceAccountReference struct { // // [RFC 1123]: https://tools.ietf.org/html/rfc1123 // - //+kubebuilder:validation:MaxLength:=253 - //+kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" + // +kubebuilder:validation:MaxLength:=253 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="name must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" + // +kubebuilder:validation:Required Name string `json:"name"` } // PreflightConfig holds the configuration for the preflight checks. If used, at least one preflight check must be non-nil. +// // +kubebuilder:validation:XValidation:rule="has(self.crdUpgradeSafety)",message="at least one of [crdUpgradeSafety] are required when preflight is specified" type PreflightConfig struct { // crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight @@ -384,167 +383,138 @@ type PreflightConfig struct { // // The CRD Upgrade Safety pre-flight check safeguards from unintended // consequences of upgrading a CRD, such as data loss. - // - // This field is required if the spec.install.preflight field is specified. CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety"` } // CRDUpgradeSafetyPreflightConfig is the configuration for CRD upgrade safety preflight check. type CRDUpgradeSafetyPreflightConfig struct { - // policy is used to configure the state of the CRD Upgrade Safety pre-flight check. - // - // This field is required when the spec.install.preflight.crdUpgradeSafety field is - // specified. + // enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. // - // Allowed values are ["Enabled", "Disabled"]. The default value is "Enabled". + // Allowed values are "None" or "Strict". The default value is "Strict". // - // When set to "Disabled", the CRD Upgrade Safety pre-flight check will be skipped + // When set to "None", the CRD Upgrade Safety pre-flight check will be skipped // when performing an upgrade operation. This should be used with caution as // unintended consequences such as data loss can occur. // - // When set to "Enabled", the CRD Upgrade Safety pre-flight check will be run when + // When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when // performing an upgrade operation. // - //+kubebuilder:validation:Enum:="Enabled";"Disabled" - //+kubebuilder:default:=Enabled - Policy CRDUpgradeSafetyPolicy `json:"policy"` + // +kubebuilder:validation:Enum:="None";"Strict" + // +kubebuilder:validation:Required + Enforcement CRDUpgradeSafetyEnforcement `json:"enforcement"` } const ( - // TODO(user): add more Types, here and into init() - TypeInstalled = "Installed" - TypeResolved = "Resolved" - TypeHealthy = "Healthy" - // TypeDeprecated is a rollup condition that is present when // any of the deprecated conditions are present. TypeDeprecated = "Deprecated" TypePackageDeprecated = "PackageDeprecated" TypeChannelDeprecated = "ChannelDeprecated" TypeBundleDeprecated = "BundleDeprecated" - TypeUnpacked = "Unpacked" - - ReasonSuccess = "Succeeded" - ReasonDeprecated = "Deprecated" - ReasonFailed = "Failed" - - ReasonErrorGettingClient = "ErrorGettingClient" - ReasonErrorGettingReleaseState = "ErrorGettingReleaseState" - - ReasonUnverifiable = "Unverifiable" - CRDUpgradeSafetyPolicyEnabled CRDUpgradeSafetyPolicy = "Enabled" - CRDUpgradeSafetyPolicyDisabled CRDUpgradeSafetyPolicy = "Disabled" + // None will not perform CRD upgrade safety checks. + CRDUpgradeSafetyEnforcementNone CRDUpgradeSafetyEnforcement = "None" + // Strict will enforce the CRD upgrade safety check and block the upgrade if the CRD would not pass the check. + CRDUpgradeSafetyEnforcementStrict CRDUpgradeSafetyEnforcement = "Strict" ) -func init() { - // TODO(user): add Types from above - conditionsets.ConditionTypes = append(conditionsets.ConditionTypes, - TypeInstalled, - TypeResolved, - TypeDeprecated, - TypePackageDeprecated, - TypeChannelDeprecated, - TypeBundleDeprecated, - TypeUnpacked, - TypeHealthy, - ) - // TODO(user): add Reasons from above - conditionsets.ConditionReasons = append(conditionsets.ConditionReasons, - ReasonSuccess, - ReasonDeprecated, - ReasonFailed, - ReasonErrorGettingClient, - ReasonErrorGettingReleaseState, - ReasonUnverifiable, - ) -} - +// BundleMetadata is a representation of the identifying attributes of a bundle. type BundleMetadata struct { - // name is a required field and is a reference - // to the name of a bundle + // name is required and follows the DNS subdomain standard + // as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + // hyphens (-) or periods (.), start and end with an alphanumeric character, + // and be no longer than 253 characters. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters" Name string `json:"name"` - // version is a required field and is a reference - // to the version that this bundle represents + + // version is a required field and is a reference to the version that this bundle represents + // version follows the semantic versioning standard as defined in https://semver.org/. + // + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self.matches(\"^([0-9]+)(\\\\.[0-9]+)?(\\\\.[0-9]+)?(-([-0-9A-Za-z]+(\\\\.[-0-9A-Za-z]+)*))?(\\\\+([-0-9A-Za-z]+(-\\\\.[-0-9A-Za-z]+)*))?\")",message="version must be well-formed semver" Version string `json:"version"` } -// ClusterExtensionStatus defines the observed state of ClusterExtension. +// ClusterExtensionStatus defines the observed state of a ClusterExtension. type ClusterExtensionStatus struct { - Install *ClusterExtensionInstallStatus `json:"install,omitempty"` - - Resolution *ClusterExtensionResolutionStatus `json:"resolution,omitempty"` - - // conditions is a representation of the current state for this ClusterExtension. - // The status is represented by a set of "conditions". - // - // Each condition is generally structured in the following format: - // - Type: a string representation of the condition type. More or less the condition "name". - // - Status: a string representation of the state of the condition. Can be one of ["True", "False", "Unknown"]. - // - Reason: a string representation of the reason for the current state of the condition. Typically useful for building automation around particular Type+Reason combinations. - // - Message: a human readable message that further elaborates on the state of the condition + // The set of condition types which apply to all spec.source variations are Installed and Progressing. // - // The global set of condition types are: - // - "Installed", represents whether or not the a bundle has been installed for this ClusterExtension - // - "Resolved", represents whether or not a bundle was found that satisfies the selection criteria outlined in the spec - // - "Unpacked", represents whether or not the bundle contents have been successfully unpacked + // The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + // When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + // When Installed is False and the Reason is Failed, the bundle has failed to install. // - // When the ClusterExtension is sourced from a catalog, the following conditions are also possible: - // - "Deprecated", represents an aggregation of the PackageDeprecated, ChannelDeprecated, and BundleDeprecated condition types - // - "PackageDeprecated", represents whether or not the package specified in the spec.source.catalog.packageName field has been deprecated - // - "ChannelDeprecated", represents whether or not any channel specified in spec.source.catalog.channels has been deprecated - // - "BundleDeprecated", represents whether or not the installed bundle is deprecated + // The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + // When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + // When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + // When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. // - // The current set of reasons are: - // - "Success", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when unpacking a bundle's content, resolution and installation/upgrading is successful - // - "Failed", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when an error has occurred while unpacking the contents of a bundle, during resolution or installation. + // When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + // These are indications from a package owner to guide users away from a particular package, channel, or bundle. + // BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + // ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + // PackageDeprecated is set if the requested package is marked deprecated in the catalog. + // Deprecated is a rollup condition that is present when any of the deprecated conditions are present. // - // - // +patchMergeKey=type - // +patchStrategy=merge // +listType=map // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` -} + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` -type ClusterExtensionInstallStatus struct { - // bundle is a representation of the currently installed bundle. + // install is a representation of the current installation status for this ClusterExtension. // - // A "bundle" is a versioned set of content that represents the resources that - // need to be applied to a cluster to install a package. - Bundle BundleMetadata `json:"bundle"` + // +optional + Install *ClusterExtensionInstallStatus `json:"install,omitempty"` } -type ClusterExtensionResolutionStatus struct { - // bundle is a representation of the bundle that was identified during - // resolution to meet all installation/upgrade constraints and is slated to be - // installed or upgraded to. +// ClusterExtensionInstallStatus is a representation of the status of the identified bundle. +type ClusterExtensionInstallStatus struct { + // bundle is a required field which represents the identifying attributes of a bundle. // // A "bundle" is a versioned set of content that represents the resources that // need to be applied to a cluster to install a package. + // + // +kubebuilder:validation:Required Bundle BundleMetadata `json:"bundle"` } -//+kubebuilder:object:root=true -//+kubebuilder:resource:scope=Cluster -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Installed Bundle",type=string,JSONPath=`.status.install.bundle.name` +// +kubebuilder:printcolumn:name=Version,type=string,JSONPath=`.status.install.bundle.version` +// +kubebuilder:printcolumn:name="Installed",type=string,JSONPath=`.status.conditions[?(@.type=='Installed')].status` +// +kubebuilder:printcolumn:name="Progressing",type=string,JSONPath=`.status.conditions[?(@.type=='Progressing')].status` +// +kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp` // ClusterExtension is the Schema for the clusterextensions API type ClusterExtension struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec ClusterExtensionSpec `json:"spec,omitempty"` + // spec is an optional field that defines the desired state of the ClusterExtension. + // +optional + Spec ClusterExtensionSpec `json:"spec,omitempty"` + + // status is an optional field that defines the observed state of the ClusterExtension. + // +optional Status ClusterExtensionStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // ClusterExtensionList contains a list of ClusterExtension type ClusterExtensionList struct { metav1.TypeMeta `json:",inline"` + + // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []ClusterExtension `json:"items"` + + // items is a required list of ClusterExtension objects. + // + // +kubebuilder:validation:Required + Items []ClusterExtension `json:"items"` } func init() { diff --git a/hack/tools/crd-generator/testdata/api/v1/groupversion_info.go b/hack/tools/crd-generator/testdata/api/v1/groupversion_info.go new file mode 100644 index 0000000000..f2e8582ee5 --- /dev/null +++ b/hack/tools/crd-generator/testdata/api/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1 contains API Schema definitions for the olm v1 API group +// +kubebuilder:object:generate=true +// +groupName=olm.operatorframework.io +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "olm.operatorframework.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml b/hack/tools/crd-generator/testdata/output/experimental/olm.operatorframework.io_clusterextensions.yaml similarity index 63% rename from config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml rename to hack/tools/crd-generator/testdata/output/experimental/olm.operatorframework.io_clusterextensions.yaml index c25c2ccb2a..9866f68bbe 100644 --- a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml +++ b/hack/tools/crd-generator/testdata/output/experimental/olm.operatorframework.io_clusterextensions.yaml @@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.1 + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental name: clusterextensions.olm.operatorframework.io spec: group: olm.operatorframework.io @@ -14,7 +15,23 @@ spec: singular: clusterextension scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 schema: openAPIV3Schema: description: ClusterExtension is the Schema for the clusterextensions API @@ -37,60 +54,21 @@ spec: metadata: type: object spec: - description: ClusterExtensionSpec defines the desired state of ClusterExtension + description: spec is an optional field that defines the desired state + of the ClusterExtension. properties: install: description: |- - install is a required field used to configure the installation options - for the ClusterExtension such as the installation namespace, - the service account and the pre-flight check configuration. - - Below is a minimal example of an installation definition (in yaml): - install: - namespace: example-namespace - serviceAccount: - name: example-sa + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. properties: - namespace: - description: |- - namespace is a reference to the Namespace in which the bundle of - content for the package referenced in the packageName field will be applied. - The bundle may contain cluster-scoped resources or resources that are - applied to other Namespaces. This Namespace is expected to exist. - - namespace is required, immutable, and follows the DNS label standard - as defined in [RFC 1123]. This means that valid values: - - Contain no more than 63 characters - - Contain only lowercase alphanumeric characters or '-' - - Start with an alphanumeric character - - End with an alphanumeric character - - Some examples of valid values are: - - some-namespace - - 123-namespace - - 1-namespace-2 - - somenamespace - - Some examples of invalid values are: - - -some-namespace - - some-namespace- - - thisisareallylongnamespacenamethatisgreaterthanthemaximumlength - - some.namespace - - [RFC 1123]: https://tools.ietf.org/html/rfc1123 - maxLength: 63 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - x-kubernetes-validations: - - message: namespace is immutable - rule: self == oldSelf preflight: description: |- - preflight is an optional field that can be used to configure the preflight checks run before installation or upgrade of the content for the package specified in the packageName field. - - When specified, it overrides the default configuration of the preflight checks that are required to execute successfully during an install/upgrade operation. + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. - When not specified, the default configuration for each preflight check will be used. + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. properties: crdUpgradeSafety: description: |- @@ -99,31 +77,25 @@ spec: The CRD Upgrade Safety pre-flight check safeguards from unintended consequences of upgrading a CRD, such as data loss. - - This field is required if the spec.install.preflight field is specified. properties: - policy: - default: Enabled + enforcement: description: |- - policy is used to configure the state of the CRD Upgrade Safety pre-flight check. - - This field is required when the spec.install.preflight.crdUpgradeSafety field is - specified. + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. - Allowed values are ["Enabled", "Disabled"]. The default value is "Enabled". + Allowed values are "None" or "Strict". The default value is "Strict". - When set to "Disabled", the CRD Upgrade Safety pre-flight check will be skipped + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped when performing an upgrade operation. This should be used with caution as unintended consequences such as data loss can occur. - When set to "Enabled", the CRD Upgrade Safety pre-flight check will be run when + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when performing an upgrade operation. enum: - - Enabled - - Disabled + - None + - Strict type: string required: - - policy + - enforcement type: object required: - crdUpgradeSafety @@ -132,56 +104,77 @@ spec: - message: at least one of [crdUpgradeSafety] are required when preflight is specified rule: has(self.crdUpgradeSafety) - serviceAccount: + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + - message: self == oldSelf + rule: namespace really is immutable + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: description: |- - serviceAccount is a required reference to a ServiceAccount that exists - in the installNamespace. The provided ServiceAccount is used to install and - manage the content for the package specified in the packageName field. - - In order to successfully install and manage the content for the package, - the ServiceAccount provided via this field should be configured with the - appropriate permissions to perform the necessary operations on all the - resources that are included in the bundle of content being applied. - properties: - name: - description: |- - name is a required, immutable reference to the name of the ServiceAccount - to be used for installation and management of the content for the package - specified in the packageName field. + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. - This ServiceAccount is expected to exist in the installNamespace. + This ServiceAccount must exist in the installNamespace. - This field follows the DNS subdomain name standard as defined in [RFC - 1123]. This means that valid values: - - Contain no more than 253 characters - - Contain only lowercase alphanumeric characters, '-', or '.' - - Start with an alphanumeric character - - End with an alphanumeric character + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. - Some examples of valid values are: - - some-serviceaccount - - 123-serviceaccount - - 1-serviceaccount-2 - - someserviceaccount - - some.serviceaccount + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount - Some examples of invalid values are: - - -some-serviceaccount - - some-serviceaccount- + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- - [RFC 1123]: https://tools.ietf.org/html/rfc1123 - maxLength: 253 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - x-kubernetes-validations: - - message: name is immutable - rule: self == oldSelf - required: - - name - type: object + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") required: - - namespace - - serviceAccount + - name type: object source: description: |- @@ -200,15 +193,22 @@ spec: properties: catalog: description: |- - catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog", - and must be the only field defined for this sourceType. + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + + This is the experimental description for Catalog properties: channels: description: |- channels is an optional reference to a set of channels belonging to the package specified in the packageName field. - A "channel" is a package author defined stream of updates for an extension. + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. When specified, it is used to constrain the set of installable bundles and the automated upgrade path. This constraint is an AND operation with the @@ -220,13 +220,6 @@ spec: When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. - This field follows the DNS subdomain name standard as defined in [RFC - 1123]. This means that valid entries: - - Contain no more than 253 characters - - Contain only lowercase alphanumeric characters, '-', or '.' - - Start with an alphanumeric character - - End with an alphanumeric character - Some examples of valid values are: - 1.1.x - alpha @@ -247,20 +240,21 @@ spec: [RFC 1123]: https://tools.ietf.org/html/rfc1123 items: maxLength: 253 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 type: array packageName: description: |- packageName is a reference to the name of the package to be installed and is used to filter the content from catalogs. - This field is required, immutable and follows the DNS subdomain name - standard as defined in [RFC 1123]. This means that valid entries: - - Contain no more than 253 characters - - Contain only lowercase alphanumeric characters, '-', or '.' - - Start with an alphanumeric character - - End with an alphanumeric character + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. Some examples of valid values are: - some-package @@ -276,11 +270,15 @@ spec: [RFC 1123]: https://tools.ietf.org/html/rfc1123 maxLength: 253 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string x-kubernetes-validations: - message: packageName is immutable rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") selector: description: |- selector is an optional field that can be used @@ -340,7 +338,7 @@ spec: the upgrade path(s) defined in the catalog are enforced for the package referenced in the packageName field. - Allowed values are: ["CatalogProvided", "SelfCertified"]. + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. When this field is set to "CatalogProvided", automatic upgrades will only occur when upgrade constraints specified by the package author are met. @@ -352,7 +350,7 @@ spec: loss. It is assumed that users have independently verified changes when using this option. - If unspecified, the default value is "CatalogProvided". + When this field is omitted, the default value is "CatalogProvided". enum: - CatalogProvided - SelfCertified @@ -433,8 +431,10 @@ spec: For more information on semver, please see https://semver.org/ maxLength: 64 - pattern: ^(\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|[x|X|\*])(\.(0|[1-9]\d*|x|X|\*]))?(\.(0|[1-9]\d*|x|X|\*))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)((?:\s+|,\s*|\s*\|\|\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\^)\s*(v?(0|[1-9]\d*|x|X|\*])(\.(0|[1-9]\d*|x|X|\*))?(\.(0|[1-9]\d*|x|X|\*]))?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)\s*)*$ type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") required: - packageName type: object @@ -442,52 +442,56 @@ spec: description: |- sourceType is a required reference to the type of install source. - Allowed values are ["Catalog"] + Allowed values are "Catalog" - When this field is set to "Catalog", information for determining the appropriate - bundle of content to install will be fetched from ClusterCatalog resources existing - on the cluster. When using the Catalog sourceType, the catalog field must also be set. + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. enum: - Catalog + - NotCatalog + type: string + test: + description: test is a required parameter type: string required: - sourceType + - test type: object x-kubernetes-validations: - - message: sourceType Catalog requires catalog field - rule: self.sourceType == 'Catalog' && has(self.catalog) + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' required: - - install + - namespace + - serviceAccount - source type: object status: - description: ClusterExtensionStatus defines the observed state of ClusterExtension. + description: status is an optional field that defines the observed state + of the ClusterExtension. properties: conditions: description: |- - conditions is a representation of the current state for this ClusterExtension. - The status is represented by a set of "conditions". - - Each condition is generally structured in the following format: - - Type: a string representation of the condition type. More or less the condition "name". - - Status: a string representation of the state of the condition. Can be one of ["True", "False", "Unknown"]. - - Reason: a string representation of the reason for the current state of the condition. Typically useful for building automation around particular Type+Reason combinations. - - Message: a human readable message that further elaborates on the state of the condition - - The global set of condition types are: - - "Installed", represents whether or not the a bundle has been installed for this ClusterExtension - - "Resolved", represents whether or not a bundle was found that satisfies the selection criteria outlined in the spec - - "Unpacked", represents whether or not the bundle contents have been successfully unpacked - - When the ClusterExtension is sourced from a catalog, the following conditions are also possible: - - "Deprecated", represents an aggregation of the PackageDeprecated, ChannelDeprecated, and BundleDeprecated condition types - - "PackageDeprecated", represents whether or not the package specified in the spec.source.catalog.packageName field has been deprecated - - "ChannelDeprecated", represents whether or not any channel specified in spec.source.catalog.channels has been deprecated - - "BundleDeprecated", represents whether or not the installed bundle is deprecated - - The current set of reasons are: - - "Success", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when unpacking a bundle's content, resolution and installation/upgrading is successful - - "Failed", this reason is set on the "Unpacked", "Resolved" and "Installed" conditions when an error has occurred while unpacking the contents of a bundle, during resolution or installation. + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -547,52 +551,37 @@ spec: - type x-kubernetes-list-type: map install: + description: install is a representation of the current installation + status for this ClusterExtension. properties: bundle: description: |- - bundle is a representation of the currently installed bundle. - - A "bundle" is a versioned set of content that represents the resources that - need to be applied to a cluster to install a package. - properties: - name: - description: |- - name is a required field and is a reference - to the name of a bundle - type: string - version: - description: |- - version is a required field and is a reference - to the version that this bundle represents - type: string - required: - - name - - version - type: object - required: - - bundle - type: object - resolution: - properties: - bundle: - description: |- - bundle is a representation of the bundle that was identified during - resolution to meet all installation/upgrade constraints and is slated to be - installed or upgraded to. + bundle is a required field which represents the identifying attributes of a bundle. A "bundle" is a versioned set of content that represents the resources that need to be applied to a cluster to install a package. properties: name: description: |- - name is a required field and is a reference - to the name of a bundle + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") version: description: |- - version is a required field and is a reference - to the version that this bundle represents + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") required: - name - version diff --git a/hack/tools/crd-generator/testdata/output/standard/olm.operatorframework.io_clusterextensions.yaml b/hack/tools/crd-generator/testdata/output/standard/olm.operatorframework.io_clusterextensions.yaml new file mode 100644 index 0000000000..8dcb4beed1 --- /dev/null +++ b/hack/tools/crd-generator/testdata/output/standard/olm.operatorframework.io_clusterextensions.yaml @@ -0,0 +1,591 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + - message: self == oldSelf + rule: namespace is immutable + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + - test + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/hack/tools/k8smaintainer/README.md b/hack/tools/k8smaintainer/README.md new file mode 100644 index 0000000000..a8162704da --- /dev/null +++ b/hack/tools/k8smaintainer/README.md @@ -0,0 +1,43 @@ +# Kubernetes Staging Module Version Synchronization Tool + +## Purpose +This tool ensures that if `k8s.io/kubernetes` changes version in your `go.mod`, all related staging modules (e.g., `k8s.io/api`, `k8s.io/apimachinery`) are automatically pinned to the corresponding published version. Recent improvements include an environment variable override and refined logic for version resolution. + +## How It Works + +1. **Parsing and Filtering:** + - Reads and parses your `go.mod` file. + - Removes existing `replace` directives for `k8s.io/` modules to avoid stale mappings. + +2. **Determine Kubernetes Version:** + - **Environment Variable Override:** + If the environment variable `K8S_IO_K8S_VERSION` is set, its value is validated (using semver standards) and used as the target version for `k8s.io/kubernetes`. The tool then runs `go get k8s.io/kubernetes@` to update the dependency. + - **Default Behavior:** + If `K8S_IO_K8S_VERSION` is not set, the tool reads the version of `k8s.io/kubernetes` from the `go.mod` file. + +3. **Compute the Target Staging Version:** + - Converts a Kubernetes version in the form `v1.xx.yy` into the staging version format `v0.xx.yy`. + - If the target staging version is unavailable, the tool attempts to fall back to the previous patch version. + +4. **Updating Module Replace Directives:** + - Retrieves the full dependency graph using `go list -m -json all`. + - Identifies relevant `k8s.io/*` modules (skipping the main module and version-suffixed modules). + - Removes outdated `replace` directives (ignoring local path replacements). + - Adds new `replace` directives to pin modules—including `k8s.io/kubernetes`—to the computed staging version. + +5. **Finalizing Changes:** + - Writes the updated `go.mod` file. + - Runs `go mod tidy` to clean up dependencies. + - Executes `go mod download k8s.io/kubernetes` to update `go.sum`. + - Logs any issues, such as modules remaining at an untagged version (`v0.0.0`), which may indicate upstream tagging problems. + +## Environment Variables + +- **K8S_IO_K8S_VERSION (optional):** + When set, this environment variable overrides the Kubernetes version found in `go.mod`. The tool validates this semver string, updates the dependency using `go get`, and processes modules accordingly. + +## Additional Notes + +- The tool ensures consistency across all `k8s.io/*` modules, even if they are not explicitly listed in `go.mod`. +- If a suitable staging version is not found, a warning is logged and the closest valid version is used. +- All operations are logged, which helps in troubleshooting and verifying the process. \ No newline at end of file diff --git a/hack/tools/k8smaintainer/main.go b/hack/tools/k8smaintainer/main.go new file mode 100644 index 0000000000..978c884a61 --- /dev/null +++ b/hack/tools/k8smaintainer/main.go @@ -0,0 +1,410 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/fs" + "log" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/blang/semver/v4" + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" +) + +const ( + k8sRepo = "k8s.io/kubernetes" + expectedMajorMinorParts = 2 + goModFilename = "go.mod" + goModFilePerms = fs.FileMode(0600) + minGoListVersionFields = 2 + minPatchNumberToDecrementFrom = 1 // We can only decrement patch if it's 1 or greater (to get 0 or greater) + k8sVersionEnvVar = "K8S_IO_K8S_VERSION" +) + +//nolint:gochecknoglobals +var goExe = "go" + +// readAndParseGoMod reads and parses the go.mod file. +func readAndParseGoMod(filename string) (*modfile.File, error) { + modBytes, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading %s: %w", filename, err) + } + modF, err := modfile.Parse(filename, modBytes, nil) + if err != nil { + return nil, fmt.Errorf("error parsing %s: %w", filename, err) + } + return modF, nil +} + +// getK8sVersionFromEnv processes the version specified via environment variable. +// It validates the version and runs `go get` to update the dependency. +func getK8sVersionFromEnv(targetK8sVer string) (string, error) { + log.Printf("Found target %s version override from env var %s: %s", k8sRepo, k8sVersionEnvVar, targetK8sVer) + if _, err := semver.ParseTolerant(targetK8sVer); err != nil { + return "", fmt.Errorf("invalid semver specified in %s: %s (%w)", k8sVersionEnvVar, targetK8sVer, err) + } + // Update the go.mod file first + log.Printf("Running 'go get %s@%s' to update the main dependency...", k8sRepo, targetK8sVer) + getArgs := fmt.Sprintf("%s@%s", k8sRepo, targetK8sVer) + if _, err := runGoCommand("get", getArgs); err != nil { + return "", fmt.Errorf("error running 'go get %s': %w", getArgs, err) + } + return targetK8sVer, nil // Return the validated version +} + +// getK8sVersionFromMod reads the go.mod file to find the current version of k8s.io/kubernetes. +// It returns the version string if found, or an empty string (and nil error) if not found. +func getK8sVersionFromMod() (string, error) { + modF, err := readAndParseGoMod(goModFilename) + if err != nil { + return "", err // Propagate error from reading/parsing + } + + // Find k8s.io/kubernetes version + for _, req := range modF.Require { + if req.Mod.Path == k8sRepo { + log.Printf("Found existing %s version in %s: %s", k8sRepo, goModFilename, req.Mod.Version) + return req.Mod.Version, nil // Return found version + } + } + // Not found case - return empty string, no error (as per original logic) + log.Printf("INFO: %s not found in %s require block. Nothing to do.", k8sRepo, goModFilename) + return "", nil +} + +func main() { + log.SetFlags(0) + if os.Getenv("GOEXE") != "" { + goExe = os.Getenv("GOEXE") + } + + wd, err := os.Getwd() + if err != nil { + log.Fatalf("Error getting working directory: %v", err) + } + modRoot := findModRoot(wd) + if modRoot == "" { + log.Fatalf("Failed to find %s in %s or parent directories", goModFilename, wd) + } + if err := os.Chdir(modRoot); err != nil { + log.Fatalf("Error changing directory to %s: %v", modRoot, err) + } + log.Printf("Running in module root: %s", modRoot) + + var k8sVer string + + // Determine the target k8s version using helper functions + targetK8sVerEnv := os.Getenv(k8sVersionEnvVar) + if targetK8sVerEnv != "" { + // Process version from environment variable + k8sVer, err = getK8sVersionFromEnv(targetK8sVerEnv) + if err != nil { + log.Fatalf("Failed to process k8s version from environment variable %s: %v", k8sVersionEnvVar, err) + } + } else { + // Process version from go.mod file + k8sVer, err = getK8sVersionFromMod() + if err != nil { + log.Fatalf("Failed to get k8s version from %s: %v", goModFilename, err) + } + // Handle the "not found" case where getK8sVersionFromMod returns "", nil + if k8sVer == "" { + os.Exit(0) // Exit gracefully as requested + } + } + + // Calculate target staging version + k8sSemVer, err := semver.ParseTolerant(k8sVer) + if err != nil { + // This should ideally not happen if validation passed earlier, but check anyway. + log.Fatalf("Invalid semver for %s: %s (%v)", k8sRepo, k8sVer, err) // Adjusted log format slightly + } + + if k8sSemVer.Major != 1 { + log.Fatalf("Expected k8s version %s to have major version 1", k8sVer) + } + targetSemVer := semver.Version{Major: 0, Minor: k8sSemVer.Minor, Patch: k8sSemVer.Patch} + // Prepend 'v' as expected by Go modules and the rest of the script logic + targetStagingVer := "v" + targetSemVer.String() + + // Validate the constructed staging version string + if _, err := semver.ParseTolerant(targetStagingVer); err != nil { + log.Fatalf("Calculated invalid staging semver: %s from k8s version %s (%v)", targetStagingVer, k8sVer, err) // Adjusted log format slightly + } + log.Printf("Target staging version calculated: %s", targetStagingVer) + + // Run `go list -m -json all` + type Module struct { + Path string + Version string + Replace *Module + Main bool + } + log.Println("Running 'go list -m -json all'...") + output, err := runGoCommand("list", "-m", "-json", "all") + if err != nil { + // Try downloading first if list fails + log.Println("'go list' failed, trying 'go mod download'...") + if _, downloadErr := runGoCommand("mod", "download"); downloadErr != nil { + log.Fatalf("Error running 'go mod download' after list failed: %v", downloadErr) + } + output, err = runGoCommand("list", "-m", "-json", "all") + if err != nil { + log.Fatalf("Error running 'go list -m -json all' even after download: %v", err) + } + } + + // Iterate, identify k8s.io/* staging modules, and determine version to pin + pins := make(map[string]string) // Module path -> version to pin + decoder := json.NewDecoder(bytes.NewReader(output)) + for decoder.More() { + var mod Module + if err := decoder.Decode(&mod); err != nil { + log.Fatalf("Error decoding go list output: %v", err) + } + + // Skip main module, non-k8s modules, k8s.io/kubernetes itself, and versioned modules like k8s.io/client-go/v2 + _, pathSuffix, _ := module.SplitPathVersion(mod.Path) // Check if path has a version suffix like /v2, /v3 etc. + if mod.Main || !strings.HasPrefix(mod.Path, "k8s.io/") || mod.Path == k8sRepo || pathSuffix != "" { + continue + } + + // Use replacement path if it exists, but skip local file replacements + effectivePath := mod.Path + if mod.Replace != nil { + // Heuristic: Assume module paths have a domain-like structure (e.g., 'xxx.yyy/zzz') in the first segment. + // Local paths usually don't (e.g., '../othermod', './local'). + parts := strings.SplitN(mod.Replace.Path, "/", 2) + if len(parts) > 0 && !strings.Contains(parts[0], ".") { + log.Printf("Skipping local replace: %s => %s", mod.Path, mod.Replace.Path) + continue + } + effectivePath = mod.Replace.Path + } + + // Check existence of target version, fallback to previous patch if needed + determinedVer, err := getLatestExistingVersion(effectivePath, targetStagingVer) + if err != nil { + log.Printf("WARNING: Error checking versions for %s: %v. Skipping pinning.", effectivePath, err) + continue + } + + if determinedVer == "" { + log.Printf("WARNING: Neither target version %s nor its predecessor found for %s. Skipping pinning.", targetStagingVer, effectivePath) + continue + } + + if determinedVer != targetStagingVer { + log.Printf("INFO: Target version %s not found for %s. Using existing predecessor version %s.", targetStagingVer, effectivePath, determinedVer) + } + + // map the original module path (as seen in the dependency graph) to the desired version for the 'replace' directive + pins[mod.Path] = determinedVer + } + + // Add k8s.io/kubernetes itself to the pins map (ensures it's covered by the replace logic) + pins[k8sRepo] = k8sVer + log.Printf("Identified %d k8s.io/* modules to manage.", len(pins)) + + // Parse go.mod again (needed in case `go list` or `go get` modified it) + modF, err := readAndParseGoMod(goModFilename) + if err != nil { + log.Fatal(err) // Error already formatted by helper function + } + + // Remove all existing k8s.io/* replaces that target other modules (not local paths) + log.Println("Removing existing k8s.io/* module replace directives...") + var replacesToRemove []string + for _, rep := range modF.Replace { + // Only remove replaces targeting k8s.io/* modules (not local replacements like ../staging) + // Check that the old path starts with k8s.io/ and the new path looks like a module path (contains '.') + if strings.HasPrefix(rep.Old.Path, "k8s.io/") && strings.Contains(rep.New.Path, ".") { + replacesToRemove = append(replacesToRemove, rep.Old.Path) + } else if strings.HasPrefix(rep.Old.Path, "k8s.io/") { + log.Printf("Note: Found existing non-module replace for %s, leaving untouched: %s => %s %s", rep.Old.Path, rep.Old.Path, rep.New.Path, rep.New.Version) + } + } + if len(replacesToRemove) > 0 { + for _, path := range replacesToRemove { + log.Printf("Removing replace for: %s", path) + // Drop replace expects oldPath and oldVersion. Version is empty for path-only replaces. + if err := modF.DropReplace(path, ""); err != nil { + // Tolerate errors if the replace was already somehow removed or structure changed + log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err) + } + } + } else { + log.Println("No existing k8s.io/* module replaces found to remove.") + } + + // Add new replace directives + log.Println("Adding determined replace directives...") + // Sort for deterministic output + sortedPaths := make([]string, 0, len(pins)) + for path := range pins { + sortedPaths = append(sortedPaths, path) + } + sort.Strings(sortedPaths) + + for _, path := range sortedPaths { + version := pins[path] + // Add replace for the module path itself (e.g., k8s.io/api => k8s.io/api v0.32.3) + if err := modF.AddReplace(path, "", path, version); err != nil { + log.Fatalf("Error adding replace for %s => %s %s: %v", path, path, version, err) + } + log.Printf("Adding replace: %s => %s %s", path, path, version) + } + + // Write go.mod + log.Println("Writing updated go.mod...") + modF.Cleanup() // Sort blocks, remove redundant directives etc. + newModBytes, err := modF.Format() + if err != nil { + log.Fatalf("Error formatting go.mod: %v", err) + } + if err := os.WriteFile(goModFilename, newModBytes, goModFilePerms); err != nil { + log.Fatalf("Error writing %s: %v", goModFilename, err) + } + + // Run `go mod tidy` + goVer := "" + if modF.Go != nil { // Ensure Go directive exists before accessing Version + goVer = modF.Go.Version + } + tidyArgs := []string{"mod", "tidy"} + if goVer != "" { + tidyArgs = append(tidyArgs, fmt.Sprintf("-go=%s", goVer)) + } + log.Printf("Running '%s %s'...", goExe, strings.Join(tidyArgs, " ")) + if _, err := runGoCommand(tidyArgs...); err != nil { + log.Fatalf("Error running 'go mod tidy': %v", err) + } + + // Run `go mod download k8s.io/kubernetes` + log.Printf("Running '%s mod download %s'...", goExe, k8sRepo) + if _, err := runGoCommand("mod", "download", k8sRepo); err != nil { + // This might not be fatal, could be network issues, but log it prominently + log.Printf("WARNING: Error running 'go mod download %s': %v", k8sRepo, err) + } + + log.Println("Successfully updated k8s dependencies.") +} + +// findModRoot searches for go.mod in dir and parent directories +func findModRoot(dir string) string { + for { + if _, err := os.Stat(filepath.Join(dir, goModFilename)); err == nil { + return dir + } + parent := filepath.Dir(dir) + if parent == dir { + return "" // Reached root + } + dir = parent + } +} + +// runGoCommand executes a go command and returns its stdout or an error +func runGoCommand(args ...string) ([]byte, error) { + cmd := exec.Command(goExe, args...) + cmd.Env = append(os.Environ(), "GO111MODULE=on") // Ensure module mode + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + log.Printf("Executing: %s %s", goExe, strings.Join(args, " ")) + if err := cmd.Run(); err != nil { + if stderr.Len() > 0 { + log.Printf("Stderr:\n%s", stderr.String()) + } + return nil, fmt.Errorf("command '%s %s' failed: %w", goExe, strings.Join(args, " "), err) + } + return stdout.Bytes(), nil +} + +// getModuleVersions retrieves the list of available versions for a module +func getModuleVersions(modulePath string) ([]string, error) { + output, err := runGoCommand("list", "-m", "-versions", modulePath) + // Combine output and error message for checking because 'go list' sometimes writes errors to stdout + combinedOutput := string(output) + if err != nil { + if !strings.Contains(combinedOutput, err.Error()) { + combinedOutput += err.Error() + } + } + + // Check if the error/output indicates "no matching versions" - this is not a fatal error for our logic + if strings.Contains(combinedOutput, "no matching versions") || strings.Contains(combinedOutput, "no required module provides package") { + log.Printf("INFO: No versions found for module %s via 'go list'.", modulePath) + return []string{}, nil // Return empty list, not an error + } + // If there was an actual error beyond "no matching versions" + if err != nil { + return nil, fmt.Errorf("error listing versions for %s: %w", modulePath, err) + } + + fields := strings.Fields(string(output)) + if len(fields) < minGoListVersionFields { + log.Printf("INFO: No versions listed for module %s (output: '%s')", modulePath, string(output)) + return []string{}, nil // No versions listed (e.g., just the module path) + } + return fields[1:], nil // First field is the module path +} + +// getLatestExistingVersion checks for targetVer and its predecessor, returning the latest one that exists +func getLatestExistingVersion(modulePath, targetVer string) (string, error) { + availableVersions, err := getModuleVersions(modulePath) + if err != nil { + return "", err + } + + foundTarget := false + for _, v := range availableVersions { + if v == targetVer { + foundTarget = true + break + } + } + + if foundTarget { + return targetVer, nil // Target version exists + } + + // Target not found, try previous patch version + targetSemVer, err := semver.ParseTolerant(targetVer) + if err != nil { + log.Printf("Could not parse target version %s for module %s: %v. Cannot determine predecessor.", targetVer, modulePath, err) + return "", nil // Cannot determine predecessor + } + + // Only try to decrement if the patch number is >= the minimum required to do so + if targetSemVer.Patch < uint64(minPatchNumberToDecrementFrom) { + log.Printf("Patch version %d is less than %d for %s, cannot determine predecessor.", targetSemVer.Patch, minPatchNumberToDecrementFrom, targetVer) + return "", nil // Cannot determine predecessor (e.g., target was v0.32.0) + } + + prevSemVer := targetSemVer + prevSemVer.Patch-- + prevPatchVer := "v" + prevSemVer.String() + + foundPrev := false + for _, v := range availableVersions { + if v == prevPatchVer { + foundPrev = true + break + } + } + + if foundPrev { + return prevPatchVer, nil // Predecessor version exists + } + + // Neither found + return "", nil +} diff --git a/hack/tools/update-crds.sh b/hack/tools/update-crds.sh new file mode 100755 index 0000000000..e379b59896 --- /dev/null +++ b/hack/tools/update-crds.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -e + +# This uses a custom CRD generator to create "standard" and "experimental" CRDs + +# The names of the generated CRDs +CE="olm.operatorframework.io_clusterextensions.yaml" +CC="olm.operatorframework.io_clustercatalogs.yaml" +CR="olm.operatorframework.io_clusterextensionrevisions.yaml" + +# order for modules and crds must match +# each item in crds must be unique, and should be associated with a module +modules=("operator-controller" "catalogd" "operator-controller") +crds=("${CE}" "${CC}" "${CR}") + +# Channels must much those in the generator +channels=("standard" "experimental") + +# Create the temp output directories +CRD_TMP=$(mktemp -d) +# Clean up the temp output directories +trap "rm -rf ${CRD_TMP}" EXIT + +for c in ${channels[@]}; do + mkdir -p ${CRD_TMP}/${c} +done + +# This calculates the controller-tools version, to keep the annotation correct +CONTROLLER_TOOLS_VER=$(go list -m sigs.k8s.io/controller-tools | awk '{print $2}') + +# Generate the CRDs +go run ./hack/tools/crd-generator ${CRD_TMP} ${CONTROLLER_TOOLS_VER} + +# Copy the generated files +for b in ${!modules[@]}; do + for c in ${channels[@]}; do + # CRDs for kinds not listed in the standardKinds map in crd-generator/main.go + # will not be generated for the standard channel - so we check the expected generated + # file exists before copying it. + FILE="${CRD_TMP}/${c}/${crds[${b}]}" + DST="helm/olmv1/base/${modules[${b}]}/crd/${c}" + if [ -e "${FILE}" ]; then + echo "$(date '+%Y/%m/%d %T') ${FILE} --> ${DST}" + mkdir -p "${DST}" + cp "${FILE}" "${DST}" + fi + done +done diff --git a/hack/tools/update-tls-profiles.sh b/hack/tools/update-tls-profiles.sh new file mode 100755 index 0000000000..8fa61c43ee --- /dev/null +++ b/hack/tools/update-tls-profiles.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -e + +if [ -z "${JQ}" ]; then + echo "JQ not defined" + exit 1 +fi + +OUTPUT=internal/shared/util/tlsprofiles/mozilla_data.go +INPUT=https://ssl-config.mozilla.org/guidelines/latest.json + +TMPFILE="$(mktemp)" +trap 'rm -rf "$TMPFILE"' EXIT + +curl -L -s ${INPUT} > ${TMPFILE} + +version=$(${JQ} -r '.version' ${TMPFILE}) + +cat > ${OUTPUT} <> ${OUTPUT} <> ${OUTPUT} + ${JQ} -r ".configurations.$1.ciphers.go[] | . |= \"tls.\" + . + \",\"" ${TMPFILE} >> ${OUTPUT} + + cat >> ${OUTPUT} <> ${OUTPUT} + + version=$(${JQ} -r ".configurations.$1.tls_versions[0]" ${TMPFILE}) + version=${version/TLSv1./tls.VersionTLS1} + version=${version/TLSv1/tls.VersionTLS10} + + cat >> ${OUTPUT} <= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml b/helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml new file mode 100644 index 0000000000..94f1d7121d --- /dev/null +++ b/helm/olmv1/base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml @@ -0,0 +1,442 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clustercatalogs.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterCatalog + listKind: ClusterCatalogList + plural: clustercatalogs + singular: clustercatalog + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastUnpacked + name: LastUnpacked + type: date + - jsonPath: .status.conditions[?(@.type=="Serving")].status + name: Serving + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is the desired state of the ClusterCatalog. + spec is required. + The controller will work to ensure that the desired + catalog is unpacked and served over the catalog content HTTP server. + properties: + availabilityMode: + default: Available + description: |- + availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + availabilityMode is optional. + + Allowed values are "Available" and "Unavailable" and omitted. + + When omitted, the default value is "Available". + + When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + and its contents as usable. + + When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + enum: + - Unavailable + - Available + type: string + priority: + default: 0 + description: |- + priority allows the user to define a priority for a ClusterCatalog. + priority is optional. + + A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + A higher number means higher priority. + + It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + + When omitted, the default priority is 0 because that is the zero value of integers. + + Negative numbers can be used to specify a priority lower than the default. + Positive numbers can be used to specify a priority higher than the default. + + The lowest possible value is -2147483648. + The highest possible value is 2147483647. + format: int32 + type: integer + source: + description: |- + source allows a user to define the source of a catalog. + A "catalog" contains information on content that can be installed on a cluster. + Providing a catalog source makes the contents of the catalog discoverable and usable by + other on-cluster components. + These on-cluster components may do a variety of things with this information, such as + presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + source is a required field. + + Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + properties: + image: + description: |- + image is used to configure how catalog contents are sourced from an OCI image. + This field is required when type is Image, and forbidden otherwise. + properties: + pollIntervalMinutes: + description: |- + pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + pollIntervalMinutes is optional. + pollIntervalMinutes can not be specified when ref is a digest-based reference. + + When omitted, the image will not be polled for new content. + minimum: 1 + type: integer + ref: + description: |- + ref allows users to define the reference to a container image containing Catalog contents. + ref is required. + ref can not be more than 1000 characters. + + A reference can be broken down into 3 parts - the domain, name, and identifier. + + The domain is typically the registry where an image is located. + It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + The port must be the last value in the domain. + Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + + The name is typically the repository in the registry where an image is located. + It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + Multiple names can be concatenated with the "/" character. + The domain and name are combined using the "/" character. + Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + + The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + For a digest-based reference, the "@" character is the separator. + For a tag-based reference, the ":" character is the separator. + An identifier is required in the reference. + + Digest-based references must contain an algorithm reference immediately after the "@" separator. + The algorithm reference must be followed by the ":" character and an encoded string. + The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + + Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + The tag must not be longer than 127 characters. + + An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != "" || self.find(':.*$') != + "" + - message: tag is invalid. the tag must not be more than 127 + characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').substring(1).size() <= 127 + : true) : true' + - message: tag is invalid. valid tags must begin with a word + character (alphanumeric + "_") followed by word characters + or ".", and "-" characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml new file mode 100644 index 0000000000..5004c8c6fd --- /dev/null +++ b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml @@ -0,0 +1,213 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensionrevisions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtensionRevision + listKind: ClusterExtensionRevisionList + plural: clusterextensionrevisions + singular: clusterextensionrevision + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtensionRevision is the Schema for the clusterextensionrevisions + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + lifecycleState: + default: Active + description: Specifies the lifecycle state of the ClusterExtensionRevision. + enum: + - Active + - Paused + - Archived + type: string + x-kubernetes-validations: + - message: can not un-archive + rule: oldSelf == 'Active' || oldSelf == 'Paused' || oldSelf == 'Archived' + && oldSelf == self + phases: + description: |- + Phases are groups of objects that will be applied at the same time. + All objects in the phase will have to pass their probes in order to progress to the next phase. + items: + description: |- + ClusterExtensionRevisionPhase are groups of objects that will be applied at the same time. + All objects in the a phase will have to pass their probes in order to progress to the next phase. + properties: + name: + description: Name identifies this phase. + maxLength: 63 + pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ + type: string + objects: + description: Objects are a list of all the objects within this + phase. + items: + description: ClusterExtensionRevisionObject contains an object + and settings for it. + properties: + collisionProtection: + default: Prevent + description: |- + CollisionProtection controls whether OLM can adopt and modify objects + already existing on the cluster or even owned by another controller. + type: string + object: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + required: + - object + type: object + type: array + required: + - name + - objects + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: phases is immutable + rule: self == oldSelf || oldSelf.size() == 0 + previous: + description: Previous references previous revisions that objects can + be adopted from. + items: + properties: + name: + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + required: + - name + - uid + type: object + type: array + x-kubernetes-validations: + - message: previous is immutable + rule: self == oldSelf + revision: + description: |- + Revision is a sequence number representing a specific revision of the ClusterExtension instance. + Must be positive. Each ClusterExtensionRevision of the same parent ClusterExtension needs to have + a unique value assigned. It is immutable after creation. The new revision number must always be previous revision +1. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: revision is immutable + rule: self == oldSelf + required: + - revision + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml new file mode 100644 index 0000000000..1038b7fdf0 --- /dev/null +++ b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml @@ -0,0 +1,629 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + config: + description: |- + config is an optional field used to specify bundle specific configuration + used to configure the bundle. Configuration is bundle specific and a bundle may provide + a configuration schema. When not specified, the default configuration of the resolved bundle will be used. + + config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide + a configuration schema the final manifests will be derived on a best-effort basis. More information on how + to configure the bundle should be found in its end-user documentation. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + inline accepts arbitrary JSON/YAML objects. + inline is validation at runtime against the schema provided by the bundle if a schema is provided. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml b/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml new file mode 100644 index 0000000000..a0983e41f9 --- /dev/null +++ b/helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml @@ -0,0 +1,590 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/olmv1/templates/_helpers.tpl b/helm/olmv1/templates/_helpers.tpl new file mode 100644 index 0000000000..89cb398934 --- /dev/null +++ b/helm/olmv1/templates/_helpers.tpl @@ -0,0 +1,65 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "olmv1.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "olmv1.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Return the name of the active component for a prefix, but _only_ if one is enabled +*/}} +{{- define "component.name.prefix" -}} +{{- if and (.Values.options.operatorController.enabled) (not .Values.options.catalogd.enabled) -}} +operator-controller- +{{- else if and (not .Values.options.operatorController.enabled) (.Values.options.catalogd.enabled) -}} +catalogd- +{{- end -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "olmv1.labels" -}} +app.kubernetes.io/part-of: olm +{{- end }} + +{{/* +Common annoations +*/}} +{{- define "olmv1.annotations" -}} +olm.operatorframework.io/feature-set: {{ .Values.options.featureSet -}}{{- if .Values.options.e2e.enabled -}}-e2e{{- end -}} +{{- end }} + +{{/* +Insertion of additional rules for RBAC +*/}} + +{{/* +Returns "operator-controller", "catalogd" or "olmv1" depending on enabled components +*/}} +{{- define "olmv1.label.name" -}} +{{- if (and .Values.options.operatorController.enabled (not .Values.options.catalogd.enabled)) -}} +operator-controller +{{- else if (and (not .Values.options.operatorController.enabled) .Values.options.catalogd.enabled) -}} +catalogd +{{- else -}} +olmv1 +{{- end -}} +{{- end -}} + +{{/* +When rendering with OpenShift, only one of the main components (catalogd, operatorController) +should be enabled +*/}} +{{- if .Values.options.openshift.enabled -}} +{{- if and .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +{{- fail "When rendering Openshift, only one of {catalogd, operatorController} should also be enabled" -}} +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml b/helm/olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml new file mode 100644 index 0000000000..7b3c2396a1 --- /dev/null +++ b/helm/olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml @@ -0,0 +1,27 @@ +{{- if .Values.options.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: olmv1-ca + namespace: {{ .Values.namespaces.certManager.name }} +spec: + commonName: olmv1-ca + isCA: true + issuerRef: + group: cert-manager.io + kind: Issuer + name: self-sign-issuer + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: olmv1-ca + secretTemplate: + annotations: + cert-manager.io/allow-direct-injection: "true" +{{- end }} diff --git a/helm/olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml b/helm/olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml new file mode 100644 index 0000000000..7c6311eedf --- /dev/null +++ b/helm/olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml @@ -0,0 +1,26 @@ +{{- if .Values.options.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + name: catalogd-service-cert + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + dnsNames: + - localhost + - catalogd-service.{{ .Values.namespaces.olmv1.name }}.svc + - catalogd-service.{{ .Values.namespaces.olmv1.name }}.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: catalogd-service-cert-git-version +{{- end }} diff --git a/helm/olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml b/helm/olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml new file mode 100644 index 0000000000..2ac8371935 --- /dev/null +++ b/helm/olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml @@ -0,0 +1,25 @@ +{{- if .Values.options.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: operator-controller-cert + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + dnsNames: + - operator-controller-service.{{ .Values.namespaces.olmv1.name }}.svc + - operator-controller-service.{{ .Values.namespaces.olmv1.name }}.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: operator-controller-cert +{{- end }} diff --git a/helm/olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml b/helm/olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml new file mode 100644 index 0000000000..57573095fd --- /dev/null +++ b/helm/olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml @@ -0,0 +1,14 @@ +{{- if .Values.options.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: olmv1-ca +spec: + ca: + secretName: olmv1-ca +{{- end }} diff --git a/helm/olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml b/helm/olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml new file mode 100644 index 0000000000..283e62c266 --- /dev/null +++ b/helm/olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml @@ -0,0 +1,14 @@ +{{- if .Values.options.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: self-sign-issuer + namespace: {{ .Values.namespaces.certManager.name }} +spec: + selfSigned: {} +{{- end }} diff --git a/helm/olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml b/helm/olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml new file mode 100644 index 0000000000..5414b93f50 --- /dev/null +++ b/helm/olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml @@ -0,0 +1,9 @@ +{{- if .Values.options.catalogd.enabled }} +{{- if (eq .Values.options.featureSet "standard") }} +{{ tpl (.Files.Get "base/catalogd/crd/standard/olm.operatorframework.io_clustercatalogs.yaml") . }} +{{- else if (eq .Values.options.featureSet "experimental") }} +{{ tpl (.Files.Get "base/catalogd/crd/experimental/olm.operatorframework.io_clustercatalogs.yaml") . }} +{{- else }} +{{- fail "options.featureSet must be set to one of: {standard,experimental}" }} +{{- end }} +{{- end }} diff --git a/helm/olmv1/templates/crds/customresourcedefinition-clusterextensionrevisions.olm.operatorframework.io.yml b/helm/olmv1/templates/crds/customresourcedefinition-clusterextensionrevisions.olm.operatorframework.io.yml new file mode 100644 index 0000000000..c006ed20f7 --- /dev/null +++ b/helm/olmv1/templates/crds/customresourcedefinition-clusterextensionrevisions.olm.operatorframework.io.yml @@ -0,0 +1,9 @@ +{{- if .Values.options.operatorController.enabled }} +{{- if (eq .Values.options.featureSet "standard") }} +{{- /* Add when GA: tpl (.Files.Get "base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensionrevisionss.yaml") . */}} +{{- else if (eq .Values.options.featureSet "experimental") }} +{{ tpl (.Files.Get "base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml") . }} +{{- else }} +{{- fail "options.featureSet must be set to one of: {standard,experimental}" }} +{{- end }} +{{- end }} diff --git a/helm/olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml b/helm/olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml new file mode 100644 index 0000000000..56e878104f --- /dev/null +++ b/helm/olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml @@ -0,0 +1,9 @@ +{{- if .Values.options.operatorController.enabled }} +{{- if (eq .Values.options.featureSet "standard") }} +{{ tpl (.Files.Get "base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml") . }} +{{- else if (eq .Values.options.featureSet "experimental") }} +{{ tpl (.Files.Get "base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml") . }} +{{- else }} +{{- fail "options.featureSet must be set to one of: {standard,experimental}" }} +{{- end }} +{{- end }} diff --git a/helm/olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml b/helm/olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml new file mode 100644 index 0000000000..b3df12139c --- /dev/null +++ b/helm/olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml @@ -0,0 +1,203 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + name: catalogd-controller-manager + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + minReadySeconds: 5 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: catalogd-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + {{- include "olmv1.annotations" . | nindent 8 }} + {{- if .Values.options.openshift.enabled }} + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + openshift.io/required-scc: privileged + {{- end }} + labels: + app.kubernetes.io/name: catalogd + control-plane: catalogd-controller-manager + {{- include "olmv1.labels" . | nindent 8 }} + {{- with .Values.options.catalogd.deployment.podLabels }} + {{- toYamlPretty . | nindent 8 }} + {{- end }} + spec: + containers: + - args: + {{- if not .Values.options.tilt.enabled }} + - --leader-elect + {{- end }} + - --metrics-bind-address=:7443 + - --external-address=catalogd-service.{{ .Values.namespaces.olmv1.name }}.svc + {{- range .Values.options.catalogd.features.enabled }} + - --feature-gates={{- . -}}=true + {{- end }} + {{- range .Values.options.catalogd.features.disabled }} + - --feature-gates={{- . -}}=false + {{- end }} + {{- range .Values.options.catalogd.deployment.extraArguments }} + - {{ . -}} + {{- end }} + {{- if .Values.options.certManager.enabled }} + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --pull-cas-dir=/var/ca-certs + {{- else if .Values.options.openshift.enabled }} + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --v=${LOG_VERBOSITY} + - --global-pull-secret=openshift-config/pull-secret + {{- end }} + {{- if .Values.options.e2e.enabled }} + {{- /* This is effectively modern with the CHACHA cipher and secp384r1 curve removed */}} + - --tls-profile=custom + - --tls-custom-version=TLSv1.3 + - --tls-custom-curves=X25519,prime256v1 + - --tls-custom-ciphers=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384 + {{- end }} + command: + - ./catalogd + {{- if or .Values.options.e2e.enabled .Values.options.openshift.enabled }} + env: + {{- if .Values.options.e2e.enabled }} + - name: GOCOVERDIR + value: /e2e-coverage + {{- end }} + {{- if .Values.options.openshift.enabled }} + - name: SSL_CERT_DIR + value: /var/ca-certs + {{- end }} + {{- end }} + image: "{{ .Values.options.catalogd.deployment.image }}" + name: manager + {{- if not .Values.options.tilt.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + {{- end }} + resources: + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + {{- if .Values.options.e2e.enabled }} + - mountPath: /e2e-coverage + name: e2e-coverage-volume + {{- end }} + - mountPath: /var/cache/ + name: cache + - mountPath: /tmp + name: tmp + {{- if .Values.options.certManager.enabled }} + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + {{- else if .Values.options.openshift.enabled }} + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + - mountPath: /etc/containers + name: etc-containers + readOnly: true + - mountPath: /etc/docker + name: etc-docker + readOnly: true + {{- end }} + {{- with .Values.deployments.containerSpec }} + {{- toYamlPretty . | nindent 10 }} + {{- end }} + serviceAccountName: catalogd-controller-manager + volumes: + {{- if .Values.options.e2e.enabled }} + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + {{- end }} + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + {{- if .Values.options.certManager.enabled }} + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogd-service-cert-git-version + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: catalogd-service-cert-git-version + {{- else if .Values.options.openshift.enabled }} + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogserver-cert + - name: ca-certs + projected: + sources: + - configMap: + items: + - key: ca-bundle.crt + path: ca-bundle.crt + name: catalogd-trusted-ca-bundle + optional: false + - configMap: + items: + - key: service-ca.crt + path: service-ca.crt + name: openshift-service-ca.crt + optional: false + - hostPath: + path: /etc/containers + type: Directory + name: etc-containers + - hostPath: + path: /etc/docker + type: Directory + name: etc-docker + {{- end }} + {{- with .Values.deployments.templateSpec }} + {{- toYamlPretty . | nindent 6 }} + {{- end }} +{{- end }} diff --git a/helm/olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml b/helm/olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml new file mode 100644 index 0000000000..9ec405a3e0 --- /dev/null +++ b/helm/olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml @@ -0,0 +1,206 @@ +{{- if .Values.options.operatorController.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + name: operator-controller-controller-manager + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: operator-controller-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + {{- include "olmv1.annotations" . | nindent 8 }} + {{- if .Values.options.openshift.enabled }} + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + openshift.io/required-scc: privileged + {{- end }} + labels: + app.kubernetes.io/name: operator-controller + control-plane: operator-controller-controller-manager + {{- include "olmv1.labels" . | nindent 8 }} + {{- with .Values.options.operatorController.deployment.podLabels }} + {{- toYamlPretty . | nindent 8 }} + {{- end }} + spec: + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + {{- if not .Values.options.tilt.enabled }} + - --leader-elect + {{- end }} + {{- range .Values.options.operatorController.features.enabled }} + - --feature-gates={{- . -}}=true + {{- end }} + {{- range .Values.options.operatorController.features.disabled }} + - --feature-gates={{- . -}}=false + {{- end }} + {{- range .Values.options.operatorController.deployment.extraArguments }} + - {{ . -}} + {{- end }} + {{- if .Values.options.certManager.enabled }} + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --pull-cas-dir=/var/ca-certs + {{- else if .Values.options.openshift.enabled }} + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --v=${LOG_VERBOSITY} + - --global-pull-secret=openshift-config/pull-secret + {{- end }} + {{- if .Values.options.e2e.enabled }} + - --tls-profile=modern + {{- end }} + command: + - /operator-controller + {{- if or .Values.options.e2e.enabled .Values.options.openshift.enabled }} + env: + {{- if .Values.options.e2e.enabled }} + - name: GOCOVERDIR + value: /e2e-coverage + {{- end }} + {{- if .Values.options.openshift.enabled }} + - name: SSL_CERT_DIR + value: /var/ca-certs + {{- end }} + {{- end }} + image: "{{ .Values.options.operatorController.deployment.image }}" + name: manager + {{- if not .Values.options.tilt.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + {{- end }} + resources: + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + {{- if .Values.options.e2e.enabled }} + - mountPath: /etc/containers + name: e2e-registries-conf + - mountPath: /e2e-coverage + name: e2e-coverage-volume + {{- end }} + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + {{- if .Values.options.certManager.enabled }} + - mountPath: /var/certs + name: operator-controller-certs + readOnly: true + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + {{- else if .Values.options.openshift.enabled }} + - mountPath: /var/certs + name: operator-controller-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + - mountPath: /etc/containers + name: etc-containers + readOnly: true + - mountPath: /etc/docker + name: etc-docker + readOnly: true + {{- end }} + {{- with .Values.deployments.containerSpec }} + {{- toYaml . | nindent 10 }} + {{- end }} + serviceAccountName: operator-controller-controller-manager + volumes: + {{- if .Values.options.e2e.enabled }} + - configMap: + name: e2e-registries-conf + name: e2e-registries-conf + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + {{- end }} + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + {{- if .Values.options.certManager.enabled }} + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: operator-controller-cert + {{- else if .Values.options.openshift.enabled }} + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + projected: + sources: + - configMap: + items: + - key: ca-bundle.crt + path: ca-bundle.crt + name: operator-controller-trusted-ca-bundle + optional: false + - configMap: + items: + - key: service-ca.crt + path: service-ca.crt + name: openshift-service-ca.crt + optional: false + - hostPath: + path: /etc/containers + type: Directory + name: etc-containers + - hostPath: + path: /etc/docker + type: Directory + name: etc-docker + {{- end }} + {{- with .Values.deployments.templateSpec }} + {{- toYamlPretty . | nindent 6 }} + {{- end }} +{{- end }} diff --git a/helm/olmv1/templates/e2e/configmap-olmv1-system-e2e-registries-conf.yml b/helm/olmv1/templates/e2e/configmap-olmv1-system-e2e-registries-conf.yml new file mode 100644 index 0000000000..d6fec9b5fb --- /dev/null +++ b/helm/olmv1/templates/e2e/configmap-olmv1-system-e2e-registries-conf.yml @@ -0,0 +1,17 @@ +{{- if .Values.options.e2e.enabled }} +apiVersion: v1 +data: + registries.conf: | + [[registry]] + prefix = "mirrored-registry.operator-controller-e2e.svc.cluster.local:5000" + location = "docker-registry.operator-controller-e2e.svc.cluster.local:5000" +kind: ConfigMap +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: e2e + {{- include "olmv1.labels" . | nindent 4 }} + name: e2e-registries-conf + namespace: {{ .Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/e2e/persistentvolumeclaim-olmv1-system-e2e-coverage.yml b/helm/olmv1/templates/e2e/persistentvolumeclaim-olmv1-system-e2e-coverage.yml new file mode 100644 index 0000000000..6f5c83fced --- /dev/null +++ b/helm/olmv1/templates/e2e/persistentvolumeclaim-olmv1-system-e2e-coverage.yml @@ -0,0 +1,18 @@ +{{- if .Values.options.e2e.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: e2e + {{- include "olmv1.labels" . | nindent 4 }} + name: e2e-coverage + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 64Mi +{{- end }} diff --git a/helm/olmv1/templates/e2e/pod-olmv1-system-e2e-coverage-copy-pod.yml b/helm/olmv1/templates/e2e/pod-olmv1-system-e2e-coverage-copy-pod.yml new file mode 100644 index 0000000000..fa4b11acaa --- /dev/null +++ b/helm/olmv1/templates/e2e/pod-olmv1-system-e2e-coverage-copy-pod.yml @@ -0,0 +1,40 @@ +{{- if .Values.options.e2e.enabled }} +apiVersion: v1 +kind: Pod +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: e2e + {{- include "olmv1.labels" . | nindent 4 }} + name: e2e-coverage-copy-pod + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + containers: + - command: + - sleep + - infinity + image: busybox:1.36 + name: tar + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /e2e-coverage + name: e2e-coverage-volume + readOnly: true + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + volumes: + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + readOnly: true +{{- end }} diff --git a/helm/olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml b/helm/olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml new file mode 100644 index 0000000000..95077c9ffe --- /dev/null +++ b/helm/olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml @@ -0,0 +1,43 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: catalogd-mutating-webhook-configuration + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + annotations: + {{- if .Values.options.certManager.enabled }} + cert-manager.io/inject-ca-from-secret: cert-manager/olmv1-ca + {{- end }} + {{- if .Values.options.openshift.enabled }} + service.beta.openshift.io/inject-cabundle: "true" + {{- end }} + {{- include "olmv1.annotations" . | nindent 4 }} +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: catalogd-service + namespace: {{ .Values.namespaces.olmv1.name }} + path: /mutate-olm-operatorframework-io-v1-clustercatalog + port: 9443 + failurePolicy: Fail + name: inject-metadata-name.olm.operatorframework.io + rules: + - apiGroups: + - olm.operatorframework.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - clustercatalogs + sideEffects: None + timeoutSeconds: 10 + matchConditions: + - name: MissingOrIncorrectMetadataNameLabel + expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" +{{- end }} diff --git a/helm/olmv1/templates/namespace.yml b/helm/olmv1/templates/namespace.yml new file mode 100644 index 0000000000..4624909d9d --- /dev/null +++ b/helm/olmv1/templates/namespace.yml @@ -0,0 +1,24 @@ +{{/* this is a common component */}} +apiVersion: v1 +kind: Namespace +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + {{- if .Values.options.openshift.enabled }} + openshift.io/node-selector: "" + workload.openshift.io/allowed: management + {{- end }} + labels: + {{- $psProfile := ternary "privileged" "restricted" .Values.options.openshift.enabled }} + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + pod-security.kubernetes.io/audit: {{ $psProfile }} + pod-security.kubernetes.io/audit-version: latest + pod-security.kubernetes.io/enforce: {{ $psProfile }} + pod-security.kubernetes.io/enforce-version: latest + pod-security.kubernetes.io/warn: {{ $psProfile }} + pod-security.kubernetes.io/warn-version: latest + {{- include "olmv1.labels" . | nindent 4 }} + {{- if .Values.options.openshift.enabled }} + openshift.io/cluster-monitoring: "true" + {{- end }} + name: {{ .Values.namespaces.olmv1.name }} diff --git a/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml new file mode 100644 index 0000000000..803e2c5943 --- /dev/null +++ b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml @@ -0,0 +1,29 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + name: catalogd-controller-manager + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + egress: + - {} + ingress: + - ports: + - port: 7443 + protocol: TCP + - port: 8443 + protocol: TCP + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + control-plane: catalogd-controller-manager + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml new file mode 100644 index 0000000000..e39a84a880 --- /dev/null +++ b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml @@ -0,0 +1,16 @@ +{{/* this is a common component */}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "component.name.prefix" . -}}default-deny-all-traffic + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml new file mode 100644 index 0000000000..fc85c57b84 --- /dev/null +++ b/helm/olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml @@ -0,0 +1,25 @@ +{{- if .Values.options.operatorController.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + name: operator-controller-controller-manager + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + egress: + - {} + ingress: + - ports: + - port: 8443 + protocol: TCP + podSelector: + matchLabels: + control-plane: operator-controller-controller-manager + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-certified-operators.yml b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-certified-operators.yml new file mode 100644 index 0000000000..86c92fd55d --- /dev/null +++ b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-certified-operators.yml @@ -0,0 +1,13 @@ +{{- if and .Values.options.openshift.enabled .Values.options.catalogd.enabled -}} +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: openshift-certified-operators +spec: + priority: -200 + source: + type: Image + image: + pollIntervalMinutes: 10 + ref: registry.redhat.io/redhat/certified-operator-index:{{- .Values.options.openshift.catalogs.version }} +{{- end -}} diff --git a/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-community-operators.yml b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-community-operators.yml new file mode 100644 index 0000000000..529af4ad51 --- /dev/null +++ b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-community-operators.yml @@ -0,0 +1,13 @@ +{{- if and .Values.options.openshift.enabled .Values.options.catalogd.enabled -}} +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: openshift-community-operators +spec: + priority: -400 + source: + type: Image + image: + pollIntervalMinutes: 10 + ref: registry.redhat.io/redhat/community-operator-index:{{- .Values.options.openshift.catalogs.version }} +{{- end -}} diff --git a/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-marketplace.yml b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-marketplace.yml new file mode 100644 index 0000000000..b8d6bcff48 --- /dev/null +++ b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-marketplace.yml @@ -0,0 +1,13 @@ +{{- if and .Values.options.openshift.enabled .Values.options.catalogd.enabled -}} +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: openshift-redhat-marketplace +spec: + priority: -300 + source: + type: Image + image: + pollIntervalMinutes: 10 + ref: registry.redhat.io/redhat/redhat-marketplace-index:{{- .Values.options.openshift.catalogs.version }} +{{- end -}} diff --git a/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-operators.yml b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-operators.yml new file mode 100644 index 0000000000..d7d94dac11 --- /dev/null +++ b/helm/olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-operators.yml @@ -0,0 +1,13 @@ +{{- if and .Values.options.openshift.enabled .Values.options.catalogd.enabled -}} +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: openshift-redhat-operators +spec: + priority: -100 + source: + type: Image + image: + pollIntervalMinutes: 10 + ref: registry.redhat.io/redhat/redhat-operator-index:{{ .Values.options.openshift.catalogs.version }} +{{- end -}} diff --git a/helm/olmv1/templates/openshift/configmap-trusted-ca.yml b/helm/olmv1/templates/openshift/configmap-trusted-ca.yml new file mode 100644 index 0000000000..b5fcf93138 --- /dev/null +++ b/helm/olmv1/templates/openshift/configmap-trusted-ca.yml @@ -0,0 +1,15 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + config.openshift.io/inject-trusted-cabundle: "true" + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-trusted-ca-bundle + namespace: {{ .Values.namespaces.olmv1.name }} +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/openshift/role-openshift-config-manager-role.yml b/helm/olmv1/templates/openshift/role-openshift-config-manager-role.yml new file mode 100644 index 0000000000..0a708152fc --- /dev/null +++ b/helm/olmv1/templates/openshift/role-openshift-config-manager-role.yml @@ -0,0 +1,23 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-manager-role + namespace: openshift-config +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/openshift/rolebinding-openshift-config-manager-rolebinding.yml b/helm/olmv1/templates/openshift/rolebinding-openshift-config-manager-rolebinding.yml new file mode 100644 index 0000000000..2209f5c579 --- /dev/null +++ b/helm/olmv1/templates/openshift/rolebinding-openshift-config-manager-rolebinding.yml @@ -0,0 +1,22 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-manager-rolebinding + namespace: openshift-config +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "olmv1.label.name" . -}}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "olmv1.label.name" . -}}-controller-manager + namespace: {{ .Values.namespaces.olmv1.name }} +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml b/helm/olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml new file mode 100644 index 0000000000..fe43d1966f --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml @@ -0,0 +1,48 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: catalogd-manager-role + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/status + verbs: + - get + - patch + - update + {{- if .Values.options.openshift.enabled }} + - apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - privileged + verbs: + - use + {{- end }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrole-common-metrics-reader.yml b/helm/olmv1/templates/rbac/clusterrole-common-metrics-reader.yml new file mode 100644 index 0000000000..069041955d --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrole-common-metrics-reader.yml @@ -0,0 +1,24 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $| nindent 4 }} + name: {{ $name -}}-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrole-common-proxy-role.yml b/helm/olmv1/templates/rbac/clusterrole-common-proxy-role.yml new file mode 100644 index 0000000000..266348e2f3 --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrole-common-proxy-role.yml @@ -0,0 +1,32 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml b/helm/olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml new file mode 100644 index 0000000000..9cd843b51d --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml @@ -0,0 +1,21 @@ +{{- if .Values.options.operatorController.enabled }} +{{/* Probably want to include this as a file somehow */}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + name: operator-controller-clusterextension-viewer-role +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - watch +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml b/helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml new file mode 100644 index 0000000000..84f221003c --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml @@ -0,0 +1,75 @@ +{{- if and .Values.options.operatorController.enabled (not (has "BoxcutterRuntime" .Values.operatorConrollerFeatures)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-controller-manager-role + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - get + - list + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/status + verbs: + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch + {{- if .Values.options.openshift.enabled }} + - apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - privileged + verbs: + - use + {{- end }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml b/helm/olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml new file mode 100644 index 0000000000..3c5f0daf4a --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml @@ -0,0 +1,20 @@ +{{- if .Values.options.catalogd.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" $ | nindent 4 }} + name: catalogd-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml b/helm/olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml new file mode 100644 index 0000000000..b53096f139 --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml @@ -0,0 +1,27 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ $name -}}-proxy-role +subjects: + - kind: ServiceAccount + name: {{ $name -}}-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml b/helm/olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml new file mode 100644 index 0000000000..9817337dff --- /dev/null +++ b/helm/olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml @@ -0,0 +1,28 @@ +{{- if .Values.options.operatorController.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" $ | nindent 4 }} +{{- if has "BoxcutterRuntime" .Values.options.operatorController.features.enabled }} + name: operator-controller-manager-admin-rolebinding +{{- else }} + name: operator-controller-manager-rolebinding +{{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole +{{- if has "BoxcutterRuntime" .Values.options.operatorController.features.enabled }} + name: cluster-admin +{{- else }} + name: operator-controller-manager-role +{{- end }} +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml b/helm/olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml new file mode 100644 index 0000000000..09cec7c0c2 --- /dev/null +++ b/helm/olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml @@ -0,0 +1,22 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: catalogd-manager-role + namespace: {{ .Values.namespaces.olmv1.name }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +{{- end }} diff --git a/helm/olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml b/helm/olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml new file mode 100644 index 0000000000..41c1c7bb73 --- /dev/null +++ b/helm/olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml @@ -0,0 +1,40 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-leader-election-role + namespace: {{ $.Values.namespaces.olmv1.name }} +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +{{- end }} diff --git a/helm/olmv1/templates/rbac/role-olmv1-system-metrics-monitor-role.yml b/helm/olmv1/templates/rbac/role-olmv1-system-metrics-monitor-role.yml new file mode 100644 index 0000000000..0a452d6b90 --- /dev/null +++ b/helm/olmv1/templates/rbac/role-olmv1-system-metrics-monitor-role.yml @@ -0,0 +1,25 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-metrics-monitor-role + namespace: {{ .Values.namespaces.olmv1.name }} +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - pods + verbs: + - get + - list + - watch +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml b/helm/olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml new file mode 100644 index 0000000000..2e31957d30 --- /dev/null +++ b/helm/olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml @@ -0,0 +1,34 @@ +{{- if .Values.options.operatorController.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-controller-manager-role + namespace: {{ .Values.namespaces.olmv1.name }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +{{- end }} diff --git a/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml new file mode 100644 index 0000000000..d8ab8f1178 --- /dev/null +++ b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml @@ -0,0 +1,28 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-leader-election-rolebinding + namespace: {{ $.Values.namespaces.olmv1.name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $name -}}-leader-election-role +subjects: + - kind: ServiceAccount + name: {{ $name -}}-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml new file mode 100644 index 0000000000..a8846104ab --- /dev/null +++ b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml @@ -0,0 +1,28 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-manager-rolebinding + namespace: {{ $.Values.namespaces.olmv1.name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $name -}}-manager-role +subjects: + - kind: ServiceAccount + name: {{ $name -}}-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/rbac/rolebinding-olmv1-system-metrics-monitor-rolebinding.yml b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-metrics-monitor-rolebinding.yml new file mode 100644 index 0000000000..18ec318a2a --- /dev/null +++ b/helm/olmv1/templates/rbac/rolebinding-olmv1-system-metrics-monitor-rolebinding.yml @@ -0,0 +1,22 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-metrics-monitor-rolebinding + namespace: {{ .Values.namespaces.olmv1.name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "olmv1.label.name" . -}}-metrics-monitor-role +subjects: + - kind: ServiceAccount + name: prometheus-k8s + namespace: openshift-monitoring +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/templates/service-olmv1-system-catalogd-service.yml b/helm/olmv1/templates/service-olmv1-system-catalogd-service.yml new file mode 100644 index 0000000000..eca9593995 --- /dev/null +++ b/helm/olmv1/templates/service-olmv1-system-catalogd-service.yml @@ -0,0 +1,31 @@ +{{- if .Values.options.catalogd.enabled }} +apiVersion: v1 +kind: Service +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + {{- if .Values.options.openshift.enabled }} + service.beta.openshift.io/serving-cert-secret-name: catalogserver-cert + {{- end }} + labels: + app.kubernetes.io/name: catalogd + {{- include "olmv1.labels" . | nindent 4 }} + name: catalogd-service + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + - name: webhook + port: 9443 + protocol: TCP + targetPort: 9443 + - name: metrics + port: 7443 + protocol: TCP + targetPort: 7443 + selector: + app.kubernetes.io/name: catalogd +{{- end }} diff --git a/helm/olmv1/templates/service-olmv1-system-operator-controller-service.yml b/helm/olmv1/templates/service-olmv1-system-operator-controller-service.yml new file mode 100644 index 0000000000..714894f4e5 --- /dev/null +++ b/helm/olmv1/templates/service-olmv1-system-operator-controller-service.yml @@ -0,0 +1,23 @@ +{{- if .Values.options.operatorController.enabled }} +apiVersion: v1 +kind: Service +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + {{- if .Values.options.openshift.enabled }} + service.beta.openshift.io/serving-cert-secret-name: operator-controller-cert + {{- end }} + labels: + app.kubernetes.io/name: operator-controller + {{- include "olmv1.labels" . | nindent 4 }} + name: operator-controller-service + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: operator-controller +{{- end }} diff --git a/helm/olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml b/helm/olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml new file mode 100644 index 0000000000..f29464ede3 --- /dev/null +++ b/helm/olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml @@ -0,0 +1,20 @@ +{{- $options := list }} +{{- if .Values.options.catalogd.enabled }} +{{- $options = append $options "catalogd" }} +{{- end }} +{{- if .Values.options.operatorController.enabled }} +{{- $options = append $options "operator-controller" }} +{{- end }} +{{- range $index, $name := $options }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + {{- include "olmv1.annotations" $ | nindent 4 }} + labels: + app.kubernetes.io/name: {{ $name }} + {{- include "olmv1.labels" $ | nindent 4 }} + name: {{ $name -}}-controller-manager + namespace: {{ $.Values.namespaces.olmv1.name }} +{{- end }} diff --git a/helm/olmv1/templates/servicemonitor-olmv1-system-metrics-monitor.yml b/helm/olmv1/templates/servicemonitor-olmv1-system-metrics-monitor.yml new file mode 100644 index 0000000000..a5bb357c37 --- /dev/null +++ b/helm/olmv1/templates/servicemonitor-olmv1-system-metrics-monitor.yml @@ -0,0 +1,33 @@ +{{- if .Values.options.openshift.enabled -}} +{{- if or .Values.options.catalogd.enabled .Values.options.operatorController.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + annotations: + {{- include "olmv1.annotations" . | nindent 4 }} + labels: + openshift.io/cluster-monitoring: 'true' + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} + {{- include "olmv1.labels" . | nindent 4 }} + name: {{ include "olmv1.label.name" . -}}-metrics-monitor + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + endpoints: + - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + interval: 30s + path: /metrics + port: metrics + scheme: https + tlsConfig: + caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt + certFile: /etc/prometheus/secrets/metrics-client-certs/tls.crt + keyFile: /etc/prometheus/secrets/metrics-client-certs/tls.key + serverName: {{ include "olmv1.label.name" . -}}-service.{{ .Values.namespaces.olmv1.name }}.svc + namespaceSelector: + matchNames: + - {{ .Values.namespaces.olmv1.name }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "olmv1.label.name" . }} +{{- end -}} +{{- end -}} diff --git a/helm/olmv1/values.yaml b/helm/olmv1/values.yaml new file mode 100644 index 0000000000..0704f43ef3 --- /dev/null +++ b/helm/olmv1/values.yaml @@ -0,0 +1,90 @@ +# Default values for OLMv1. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# List of components to include +options: + operatorController: + enabled: true + deployment: + image: quay.io/operator-framework/operator-controller:devel + extraArguments: [] + features: + enabled: [] + disabled: [] + catalogd: + enabled: true + deployment: + image: quay.io/operator-framework/catalogd:devel + extraArguments: [] + features: + enabled: [] + disabled: [] + certManager: + enabled: false + e2e: + enabled: false + tilt: + enabled: false + openshift: + enabled: false + catalogs: + version: v4.20 + # This can be one of: standard or experimental + featureSet: standard + +# The set of namespaces +namespaces: + olmv1: + name: olmv1-system + certManager: + name: cert-manager + +# Common deployment values for operator-controller and catalogd +deployments: + templateSpec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 + containerSpec: + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError diff --git a/helm/prometheus/Chart.yaml b/helm/prometheus/Chart.yaml new file mode 100644 index 0000000000..1cd44e76c5 --- /dev/null +++ b/helm/prometheus/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: prometheus +description: A Helm chart of Prometheus resources for OLMv1 +icon: https://raw.githubusercontent.com/operator-framework/operator-framework.io/refs/heads/master/static/tile70x70.png + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 diff --git a/helm/prometheus/templates/clusterrole-prometheus.yml b/helm/prometheus/templates/clusterrole-prometheus.yml new file mode 100644 index 0000000000..d109c2660a --- /dev/null +++ b/helm/prometheus/templates/clusterrole-prometheus.yml @@ -0,0 +1,44 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: prometheus +rules: + - apiGroups: + - "" + resources: + - nodes + - nodes/metrics + - services + - endpoints + - pods + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - nonResourceURLs: + - /metrics + verbs: + - get diff --git a/helm/prometheus/templates/clusterrolebinding-prometheus.yml b/helm/prometheus/templates/clusterrolebinding-prometheus.yml new file mode 100644 index 0000000000..eb5b43547c --- /dev/null +++ b/helm/prometheus/templates/clusterrolebinding-prometheus.yml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: prometheus +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: prometheus +subjects: + - kind: ServiceAccount + name: prometheus + namespace: {{ .Values.namespaces.olmv1.name }} diff --git a/helm/prometheus/templates/networkpolicy-prometheus.yml b/helm/prometheus/templates/networkpolicy-prometheus.yml new file mode 100644 index 0000000000..821e7054b2 --- /dev/null +++ b/helm/prometheus/templates/networkpolicy-prometheus.yml @@ -0,0 +1,17 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: prometheus + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + egress: + - {} + ingress: + - {} + podSelector: + matchLabels: + app.kubernetes.io/name: prometheus + policyTypes: + - Egress + - Ingress diff --git a/helm/prometheus/templates/prometheus-prometheus.yml b/helm/prometheus/templates/prometheus-prometheus.yml new file mode 100644 index 0000000000..3b9df82d1f --- /dev/null +++ b/helm/prometheus/templates/prometheus-prometheus.yml @@ -0,0 +1,19 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: Prometheus +metadata: + name: prometheus + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + logLevel: debug + ruleSelector: {} + scrapeInterval: 1m + scrapeTimeout: 30s + securityContext: + runAsNonRoot: true + runAsUser: 65534 + seccompProfile: + type: RuntimeDefault + serviceAccountName: prometheus + serviceDiscoveryRole: EndpointSlice + serviceMonitorSelector: {} diff --git a/helm/prometheus/templates/prometheusrile-controller-alerts.yml b/helm/prometheus/templates/prometheusrile-controller-alerts.yml new file mode 100644 index 0000000000..bce2706eea --- /dev/null +++ b/helm/prometheus/templates/prometheusrile-controller-alerts.yml @@ -0,0 +1,72 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: controller-alerts + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + groups: + - name: controller-panic + rules: + - alert: reconciler-panic + annotations: + description: controller of pod {{`{{ $labels.pod }}`}} experienced panic(s); count={{`{{ $value }}`}} + expr: controller_runtime_reconcile_panics_total{} > 0 + - alert: webhook-panic + annotations: + description: controller webhook of pod {{`{{ $labels.pod }}`}} experienced panic(s); count={{`{{ $value }}`}} + expr: controller_runtime_webhook_panics_total{} > 0 + - name: resource-usage + rules: + - alert: oom-events + annotations: + description: container {{`{{ $labels.container }}`}} of pod {{`{{ $labels.pod }}`}} experienced OOM event(s); count={{`{{ $value }}`}} + expr: container_oom_events_total > 0 + - alert: operator-controller-memory-growth + annotations: + description: 'operator-controller pod memory usage growing at a high rate for 5 minutes: {{`{{ $value | humanize }}`}}B/sec' + expr: deriv(sum(container_memory_working_set_bytes{pod=~"operator-controller.*",container="manager"})[5m:]) > 100_000 + for: 5m + keep_firing_for: 1d + - alert: catalogd-memory-growth + annotations: + description: 'catalogd pod memory usage growing at a high rate for 5 minutes: {{`{{ $value | humanize }}`}}B/sec' + expr: deriv(sum(container_memory_working_set_bytes{pod=~"catalogd.*",container="manager"})[5m:]) > 100_000 + for: 5m + keep_firing_for: 1d + - alert: operator-controller-memory-usage + annotations: + description: 'operator-controller pod using high memory resources for the last 5 minutes: {{`{{ $value | humanize }}`}}B' + expr: sum(container_memory_working_set_bytes{pod=~"operator-controller.*",container="manager"}) > 100_000_000 + for: 5m + keep_firing_for: 1d + - alert: catalogd-memory-usage + annotations: + description: 'catalogd pod using high memory resources for the last 5 minutes: {{`{{ $value | humanize }}`}}B' + expr: sum(container_memory_working_set_bytes{pod=~"catalogd.*",container="manager"}) > 75_000_000 + for: 5m + keep_firing_for: 1d + - alert: operator-controller-cpu-usage + annotations: + description: 'operator-controller using high cpu resource for 5 minutes: {{`{{ $value | printf "%.2f" }}`}}%' + expr: rate(container_cpu_usage_seconds_total{pod=~"operator-controller.*",container="manager"}[5m]) * 100 > 20 + for: 5m + keep_firing_for: 1d + - alert: catalogd-cpu-usage + annotations: + description: 'catalogd using high cpu resources for 5 minutes: {{`{{ $value | printf "%.2f" }}`}}%' + expr: rate(container_cpu_usage_seconds_total{pod=~"catalogd.*",container="manager"}[5m]) * 100 > 20 + for: 5m + keep_firing_for: 1d + - alert: operator-controller-api-call-rate + annotations: + description: 'operator-controller making excessive API calls for 5 minutes: {{`{{ $value | printf "%.2f" }}`}}/sec' + expr: sum(rate(rest_client_requests_total{job=~"operator-controller-service"}[5m])) > 10 + for: 5m + keep_firing_for: 1d + - alert: catalogd-api-call-rate + annotations: + description: 'catalogd making excessive API calls for 5 minutes: {{`{{ $value | printf "%.2f" }}`}}/sec' + expr: sum(rate(rest_client_requests_total{job=~"catalogd-service"}[5m])) > 5 + for: 5m + keep_firing_for: 1d diff --git a/helm/prometheus/templates/secret-prometheus-metrics-token.yml b/helm/prometheus/templates/secret-prometheus-metrics-token.yml new file mode 100644 index 0000000000..9db86fe995 --- /dev/null +++ b/helm/prometheus/templates/secret-prometheus-metrics-token.yml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/service-account.name: prometheus + name: prometheus-metrics-token + namespace: {{ .Values.namespaces.olmv1.name }} +type: kubernetes.io/service-account-token diff --git a/helm/prometheus/templates/service-prometheus-service.yml b/helm/prometheus/templates/service-prometheus-service.yml new file mode 100644 index 0000000000..11c0555104 --- /dev/null +++ b/helm/prometheus/templates/service-prometheus-service.yml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: prometheus-service + namespace: {{ .Values.namespaces.prometheus.name }} +spec: + ports: + - name: web + nodePort: 30900 + port: 9090 + protocol: TCP + targetPort: web + selector: + prometheus: prometheus + type: NodePort diff --git a/helm/prometheus/templates/serviceaccount-prometheus.yml b/helm/prometheus/templates/serviceaccount-prometheus.yml new file mode 100644 index 0000000000..da68eb7aae --- /dev/null +++ b/helm/prometheus/templates/serviceaccount-prometheus.yml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: prometheus + namespace: {{ .Values.namespaces.olmv1.name }} diff --git a/helm/prometheus/templates/servicemonitor-catalogd-controller-manager-metrics-monitor.yml b/helm/prometheus/templates/servicemonitor-catalogd-controller-manager-metrics-monitor.yml new file mode 100644 index 0000000000..b3fd498547 --- /dev/null +++ b/helm/prometheus/templates/servicemonitor-catalogd-controller-manager-metrics-monitor.yml @@ -0,0 +1,33 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: catalogd-controller-manager-metrics-monitor + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + endpoints: + - authorization: + credentials: + key: token + name: prometheus-metrics-token + interval: 10s + path: /metrics + port: metrics + scheme: https + tlsConfig: + ca: + secret: + key: ca.crt + name: catalogd-service-cert-git-version + cert: + secret: + key: tls.crt + name: catalogd-service-cert-git-version + insecureSkipVerify: false + keySecret: + key: tls.key + name: catalogd-service-cert-git-version + serverName: catalogd-service.{{ .Values.namespaces.olmv1.name }}.svc + selector: + matchLabels: + app.kubernetes.io/name: catalogd diff --git a/helm/prometheus/templates/servicemonitor-kubelet.yml b/helm/prometheus/templates/servicemonitor-kubelet.yml new file mode 100644 index 0000000000..18d078a1f9 --- /dev/null +++ b/helm/prometheus/templates/servicemonitor-kubelet.yml @@ -0,0 +1,45 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + k8s-app: kubelet + name: kubelet + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + endpoints: + - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + honorLabels: true + interval: 10s + metricRelabelings: + - action: keep + regex: (operator-controller|catalogd).*;manager + sourceLabels: + - pod + - container + path: /metrics + port: https-metrics + scheme: https + tlsConfig: + insecureSkipVerify: true + - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + honorLabels: true + interval: 10s + metricRelabelings: + - action: keep + regex: (operator-controller|catalogd).*;manager + sourceLabels: + - pod + - container + path: /metrics/cadvisor + port: https-metrics + scheme: https + tlsConfig: + insecureSkipVerify: true + jobLabel: k8s-app + namespaceSelector: + matchNames: + - kube-system + selector: + matchLabels: + k8s-app: kubelet diff --git a/helm/prometheus/templates/servicemonitor-operator-controller-controller-manager-metrics-monitor.yml b/helm/prometheus/templates/servicemonitor-operator-controller-controller-manager-metrics-monitor.yml new file mode 100644 index 0000000000..b77a090b22 --- /dev/null +++ b/helm/prometheus/templates/servicemonitor-operator-controller-controller-manager-metrics-monitor.yml @@ -0,0 +1,33 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: operator-controller-controller-manager-metrics-monitor + namespace: {{ .Values.namespaces.olmv1.name }} +spec: + endpoints: + - authorization: + credentials: + key: token + name: prometheus-metrics-token + interval: 10s + path: /metrics + port: https + scheme: https + tlsConfig: + ca: + secret: + key: ca.crt + name: olmv1-cert + cert: + secret: + key: tls.crt + name: olmv1-cert + insecureSkipVerify: false + keySecret: + key: tls.key + name: olmv1-cert + serverName: operator-controller-service.{{ .Values.namespaces.olmv1.name }}.svc + selector: + matchLabels: + control-plane: operator-controller-controller-manager diff --git a/helm/prometheus/values.yaml b/helm/prometheus/values.yaml new file mode 100644 index 0000000000..d73579da8d --- /dev/null +++ b/helm/prometheus/values.yaml @@ -0,0 +1,19 @@ +# Default values for OLMv1. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# List of components to include +options: + operatorController: + enabled: true + catalogd: + enabled: true + +# The set of namespaces +namespaces: + olmv1: + name: olmv1-system + prometheus: + name: olmv1-system + certManager: + name: cert-manager diff --git a/helm/tilt.yaml b/helm/tilt.yaml new file mode 100644 index 0000000000..aaed7c71fb --- /dev/null +++ b/helm/tilt.yaml @@ -0,0 +1,25 @@ +# experimental values for OLMv1. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Tilt is an exception to the multi-values case, +# as the Tilt runner only accepts a single values file + +options: + certManager: + enabled: true + tilt: + enabled: true + featureSet: experimental + operatorController: + features: + enabled: + - SingleOwnNamespaceInstallSupport + - PreflightPermissions + - HelmChartSupport + disabled: + - WebhookProviderOpenshiftServiceCA + catalogd: + features: + enabled: + - APIV1MetasHandler diff --git a/internal/action/restconfig.go b/internal/action/restconfig.go deleted file mode 100644 index a034ebb211..0000000000 --- a/internal/action/restconfig.go +++ /dev/null @@ -1,32 +0,0 @@ -package action - -import ( - "context" - "net/http" - - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/authentication" -) - -func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { - return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { - cExt := o.(*ocv1alpha1.ClusterExtension) - saKey := types.NamespacedName{ - Name: cExt.Spec.Install.ServiceAccount.Name, - Namespace: cExt.Spec.Install.Namespace, - } - saConfig := rest.AnonymousClientConfig(c) - saConfig.Wrap(func(rt http.RoundTripper) http.RoundTripper { - return &authentication.TokenInjectingRoundTripper{ - Tripper: rt, - TokenGetter: tokenGetter, - Key: saKey, - } - }) - return saConfig, nil - } -} diff --git a/internal/applier/helm.go b/internal/applier/helm.go deleted file mode 100644 index ac43726f17..0000000000 --- a/internal/applier/helm.go +++ /dev/null @@ -1,203 +0,0 @@ -package applier - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "io/fs" - "strings" - - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - apimachyaml "k8s.io/apimachinery/pkg/util/yaml" - "sigs.k8s.io/controller-runtime/pkg/client" - - helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/rukpak/convert" - "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" - "github.com/operator-framework/operator-controller/internal/rukpak/util" -) - -const ( - StateNeedsInstall string = "NeedsInstall" - StateNeedsUpgrade string = "NeedsUpgrade" - StateUnchanged string = "Unchanged" - StateError string = "Error" - maxHelmReleaseHistory = 10 -) - -// Preflight is a check that should be run before making any changes to the cluster -type Preflight interface { - // Install runs checks that should be successful prior - // to installing the Helm release. It is provided - // a Helm release and returns an error if the - // check is unsuccessful - Install(context.Context, *release.Release) error - - // Upgrade runs checks that should be successful prior - // to upgrading the Helm release. It is provided - // a Helm release and returns an error if the - // check is unsuccessful - Upgrade(context.Context, *release.Release) error -} - -type Helm struct { - ActionClientGetter helmclient.ActionClientGetter - Preflights []Preflight -} - -func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1alpha1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) ([]client.Object, string, error) { - chrt, err := convert.RegistryV1ToHelmChart(ctx, contentFS, ext.Spec.Install.Namespace, []string{corev1.NamespaceAll}) - if err != nil { - return nil, "", err - } - values := chartutil.Values{} - - ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) - if err != nil { - return nil, "", err - } - - post := &postrenderer{ - labels: objectLabels, - } - - rel, desiredRel, state, err := h.getReleaseState(ac, ext, chrt, values, post) - if err != nil { - return nil, "", err - } - - for _, preflight := range h.Preflights { - if ext.Spec.Install.Preflight != nil && ext.Spec.Install.Preflight.CRDUpgradeSafety != nil { - if _, ok := preflight.(*crdupgradesafety.Preflight); ok && ext.Spec.Install.Preflight.CRDUpgradeSafety.Policy == ocv1alpha1.CRDUpgradeSafetyPolicyDisabled { - // Skip this preflight check because it is of type *crdupgradesafety.Preflight and the CRD Upgrade Safety - // preflight check has been disabled - continue - } - } - switch state { - case StateNeedsInstall: - err := preflight.Install(ctx, desiredRel) - if err != nil { - return nil, state, err - } - case StateNeedsUpgrade: - err := preflight.Upgrade(ctx, desiredRel) - if err != nil { - return nil, state, err - } - } - } - - switch state { - case StateNeedsInstall: - rel, err = ac.Install(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(install *action.Install) error { - install.CreateNamespace = false - install.Labels = storageLabels - return nil - }, helmclient.AppendInstallPostRenderer(post)) - if err != nil { - return nil, state, err - } - case StateNeedsUpgrade: - rel, err = ac.Upgrade(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(upgrade *action.Upgrade) error { - upgrade.MaxHistory = maxHelmReleaseHistory - upgrade.Labels = storageLabels - return nil - }, helmclient.AppendUpgradePostRenderer(post)) - if err != nil { - return nil, state, err - } - case StateUnchanged: - if err := ac.Reconcile(rel); err != nil { - return nil, state, err - } - default: - return nil, state, fmt.Errorf("unexpected release state %q", state) - } - - relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) - if err != nil { - return nil, state, err - } - - return relObjects, state, nil -} - -func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1alpha1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, *release.Release, string, error) { - currentRelease, err := cl.Get(ext.GetName()) - if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { - return nil, nil, StateError, err - } - if errors.Is(err, driver.ErrReleaseNotFound) { - return nil, nil, StateNeedsInstall, nil - } - - if errors.Is(err, driver.ErrReleaseNotFound) { - desiredRelease, err := cl.Install(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(i *action.Install) error { - i.DryRun = true - i.DryRunOption = "server" - return nil - }, helmclient.AppendInstallPostRenderer(post)) - if err != nil { - return nil, nil, StateError, err - } - return nil, desiredRelease, StateNeedsInstall, nil - } - desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.Install.Namespace, chrt, values, func(upgrade *action.Upgrade) error { - upgrade.MaxHistory = maxHelmReleaseHistory - upgrade.DryRun = true - upgrade.DryRunOption = "server" - return nil - }, helmclient.AppendUpgradePostRenderer(post)) - if err != nil { - return currentRelease, nil, StateError, err - } - relState := StateUnchanged - if desiredRelease.Manifest != currentRelease.Manifest || - currentRelease.Info.Status == release.StatusFailed || - currentRelease.Info.Status == release.StatusSuperseded { - relState = StateNeedsUpgrade - } - return currentRelease, desiredRelease, relState, nil -} - -type postrenderer struct { - labels map[string]string - cascade postrender.PostRenderer -} - -func (p *postrenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { - var buf bytes.Buffer - dec := apimachyaml.NewYAMLOrJSONDecoder(renderedManifests, 1024) - for { - obj := unstructured.Unstructured{} - err := dec.Decode(&obj) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, err - } - obj.SetLabels(util.MergeMaps(obj.GetLabels(), p.labels)) - b, err := obj.MarshalJSON() - if err != nil { - return nil, err - } - buf.Write(b) - } - if p.cascade != nil { - return p.cascade.Run(&buf) - } - return &buf, nil -} diff --git a/internal/catalogd/OWNERS b/internal/catalogd/OWNERS new file mode 100644 index 0000000000..84b5bd1cca --- /dev/null +++ b/internal/catalogd/OWNERS @@ -0,0 +1,2 @@ +approvers: + - catalogd-approvers diff --git a/internal/catalogd/controllers/core/clustercatalog_controller.go b/internal/catalogd/controllers/core/clustercatalog_controller.go new file mode 100644 index 0000000000..e968db7b97 --- /dev/null +++ b/internal/catalogd/controllers/core/clustercatalog_controller.go @@ -0,0 +1,466 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "context" // #nosec + "errors" + "fmt" + "slices" + "sync" + "time" + + "go.podman.io/image/v5/docker/reference" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/catalogd/storage" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" +) + +const ( + fbcDeletionFinalizer = "olm.operatorframework.io/delete-server-cache" + // CatalogSources are polled if PollInterval is mentioned, in intervals of wait.Jitter(pollDuration, maxFactor) + // wait.Jitter returns a time.Duration between pollDuration and pollDuration + maxFactor * pollDuration. + requeueJitterMaxFactor = 0.01 +) + +// ClusterCatalogReconciler reconciles a Catalog object +type ClusterCatalogReconciler struct { + client.Client + + ImageCache imageutil.Cache + ImagePuller imageutil.Puller + + Storage storage.Instance + + finalizers crfinalizer.Finalizers + + // TODO: The below storedCatalogs fields are used for a quick a hack that helps + // us correctly populate a ClusterCatalog's status. The fact that we need + // these is indicative of a larger problem with the design of one or both + // of the Unpacker and Storage interfaces. We should fix this. + storedCatalogsMu sync.RWMutex + storedCatalogs map[string]storedCatalogData +} + +type storedCatalogData struct { + ref reference.Canonical + lastUnpack time.Time + lastSuccessfulPoll time.Time + observedGeneration int64 +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile +func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx).WithName("catalogd-controller") + ctx = log.IntoContext(ctx, l) + + l.Info("reconcile starting") + defer l.Info("reconcile ending") + + existingCatsrc := ocv1.ClusterCatalog{} + if err := r.Get(ctx, req.NamespacedName, &existingCatsrc); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + reconciledCatsrc := existingCatsrc.DeepCopy() + res, reconcileErr := r.reconcile(ctx, reconciledCatsrc) + + // If we encounter an error, we should delete the stored catalog metadata + // which represents the state of a successfully unpacked catalog. Deleting + // this state ensures that we will continue retrying the unpacking process + // until it succeeds. + if reconcileErr != nil { + r.deleteStoredCatalog(reconciledCatsrc.Name) + } + + // Do checks before any Update()s, as Update() may modify the resource structure! + updateStatus := !equality.Semantic.DeepEqual(existingCatsrc.Status, reconciledCatsrc.Status) + updateFinalizers := !equality.Semantic.DeepEqual(existingCatsrc.Finalizers, reconciledCatsrc.Finalizers) + unexpectedFieldsChanged := checkForUnexpectedFieldChange(existingCatsrc, *reconciledCatsrc) + + if unexpectedFieldsChanged { + panic("spec or metadata changed by reconciler") + } + + // Save the finalizers off to the side. If we update the status, the reconciledCatsrc will be updated + // to contain the new state of the ClusterCatalog, which contains the status update, but (critically) + // does not contain the finalizers. After the status update, we need to re-add the finalizers to the + // reconciledCatsrc before updating the object. + finalizers := reconciledCatsrc.Finalizers + + if updateStatus { + if err := r.Client.Status().Update(ctx, reconciledCatsrc); err != nil { + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err)) + } + } + + reconciledCatsrc.Finalizers = finalizers + + if updateFinalizers { + if err := r.Update(ctx, reconciledCatsrc); err != nil { + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err)) + } + } + + return res, reconcileErr +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.storedCatalogsMu.Lock() + defer r.storedCatalogsMu.Unlock() + r.storedCatalogs = make(map[string]storedCatalogData) + + if err := r.setupFinalizers(); err != nil { + return fmt.Errorf("failed to setup finalizers: %v", err) + } + + return ctrl.NewControllerManagedBy(mgr). + For(&ocv1.ClusterCatalog{}). + Named("catalogd-clustercatalog-controller"). + Complete(r) +} + +// Note: This function always returns ctrl.Result{}. The linter +// fusses about this as we could instead just return error. This was +// discussed in https://github.com/operator-framework/rukpak/pull/635#discussion_r1229859464 +// and the consensus was that it is better to keep the ctrl.Result return +// type so that if we do end up needing to return something else we don't forget +// to add the ctrl.Result type back as a return value. Adding a comment to ignore +// linting from the linter that was fussing about this. +// nolint:unparam +func (r *ClusterCatalogReconciler) reconcile(ctx context.Context, catalog *ocv1.ClusterCatalog) (ctrl.Result, error) { + l := log.FromContext(ctx) + // Check if the catalog availability is set to disabled, if true then + // unset base URL, delete it from the cache and set appropriate status + if catalog.Spec.AvailabilityMode == ocv1.AvailabilityModeUnavailable { + // Delete the catalog from local cache + err := r.deleteCatalogCache(ctx, catalog) + if err != nil { + return ctrl.Result{}, err + } + + // Set status.conditions[type=Progressing] to False as we are done with + // all that needs to be done with the catalog + updateStatusProgressingUserSpecifiedUnavailable(&catalog.Status, catalog.GetGeneration()) + + // Remove the fbcDeletionFinalizer as we do not want a finalizer attached to the catalog + // when it is disabled. Because the finalizer serves no purpose now. + controllerutil.RemoveFinalizer(catalog, fbcDeletionFinalizer) + + return ctrl.Result{}, nil + } + + finalizeResult, err := r.finalizers.Finalize(ctx, catalog) + if err != nil { + return ctrl.Result{}, err + } + if finalizeResult.Updated || finalizeResult.StatusUpdated { + // On create: make sure the finalizer is applied before we do anything + // On delete: make sure we do nothing after the finalizer is removed + return ctrl.Result{}, nil + } + + if catalog.GetDeletionTimestamp() != nil { + // If we've gotten here, that means the cluster catalog is being deleted, we've handled all of + // _our_ finalizers (above), but the cluster catalog is still present in the cluster, likely + // because there are _other_ finalizers that other controllers need to handle, (e.g. the orphan + // deletion finalizer). + return ctrl.Result{}, nil + } + + // TODO: The below algorithm to get the current state based on an in-memory + // storedCatalogs map is a hack that helps us keep the ClusterCatalog's + // status up-to-date. The fact that we need this setup is indicative of + // a larger problem with the design of one or both of the Unpacker and + // Storage interfaces and/or their interactions. We should fix this. + expectedStatus, storedCatalog, hasStoredCatalog := r.getCurrentState(catalog) + + // If any of the following are true, we need to unpack the catalog: + // - we don't have a stored catalog in the map + // - we have a stored catalog, but the content doesn't exist on disk + // - we have a stored catalog, the content exists, but the expected status differs from the actual status + // - we have a stored catalog, the content exists, the status looks correct, but the catalog generation is different from the observed generation in the stored catalog + // - we have a stored catalog, the content exists, the status looks correct and reflects the catalog generation, but it is time to poll again + needsUnpack := false + switch { + case !hasStoredCatalog: + l.Info("unpack required: no cached catalog metadata found for this catalog") + needsUnpack = true + case !r.Storage.ContentExists(catalog.Name): + l.Info("unpack required: no stored content found for this catalog") + needsUnpack = true + case !equality.Semantic.DeepEqual(catalog.Status, *expectedStatus): + l.Info("unpack required: current ClusterCatalog status differs from expected status") + needsUnpack = true + case catalog.Generation != storedCatalog.observedGeneration: + l.Info("unpack required: catalog generation differs from observed generation") + needsUnpack = true + case r.needsPoll(storedCatalog.lastSuccessfulPoll, catalog): + l.Info("unpack required: poll duration has elapsed") + needsUnpack = true + } + + if !needsUnpack { + // No need to update the status because we've already checked + // that it is set correctly. Otherwise, we'd be unpacking again. + return nextPollResult(storedCatalog.lastSuccessfulPoll, catalog), nil + } + + if catalog.Spec.Source.Type != ocv1.SourceTypeImage { + err := reconcile.TerminalError(fmt.Errorf("unknown source type %q", catalog.Spec.Source.Type)) + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), err) + return ctrl.Result{}, err + } + if catalog.Spec.Source.Image == nil { + err := reconcile.TerminalError(fmt.Errorf("error parsing ClusterCatalog %q, image source is nil", catalog.Name)) + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), err) + return ctrl.Result{}, err + } + + fsys, canonicalRef, unpackTime, err := r.ImagePuller.Pull(ctx, catalog.Name, catalog.Spec.Source.Image.Ref, r.ImageCache) + if err != nil { + unpackErr := fmt.Errorf("source catalog content: %w", err) + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), unpackErr) + return ctrl.Result{}, unpackErr + } + + // TODO: We should check to see if the unpacked result has the same content + // as the already unpacked content. If it does, we should skip this rest + // of the unpacking steps. + if err := r.Storage.Store(ctx, catalog.Name, fsys); err != nil { + storageErr := fmt.Errorf("error storing fbc: %v", err) + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), storageErr) + return ctrl.Result{}, storageErr + } + baseURL := r.Storage.BaseURL(catalog.Name) + + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), nil) + updateStatusServing(&catalog.Status, canonicalRef, unpackTime, baseURL, catalog.GetGeneration()) + + lastSuccessfulPoll := time.Now() + r.storedCatalogsMu.Lock() + r.storedCatalogs[catalog.Name] = storedCatalogData{ + ref: canonicalRef, + lastUnpack: unpackTime, + lastSuccessfulPoll: lastSuccessfulPoll, + observedGeneration: catalog.GetGeneration(), + } + r.storedCatalogsMu.Unlock() + return nextPollResult(lastSuccessfulPoll, catalog), nil +} + +func (r *ClusterCatalogReconciler) getCurrentState(catalog *ocv1.ClusterCatalog) (*ocv1.ClusterCatalogStatus, storedCatalogData, bool) { + r.storedCatalogsMu.RLock() + storedCatalog, hasStoredCatalog := r.storedCatalogs[catalog.Name] + r.storedCatalogsMu.RUnlock() + + expectedStatus := catalog.Status.DeepCopy() + + // Set expected status based on what we see in the stored catalog + clearUnknownConditions(expectedStatus) + if hasStoredCatalog && r.Storage.ContentExists(catalog.Name) { + updateStatusServing(expectedStatus, storedCatalog.ref, storedCatalog.lastUnpack, r.Storage.BaseURL(catalog.Name), storedCatalog.observedGeneration) + updateStatusProgressing(expectedStatus, storedCatalog.observedGeneration, nil) + } + + return expectedStatus, storedCatalog, hasStoredCatalog +} + +func nextPollResult(lastSuccessfulPoll time.Time, catalog *ocv1.ClusterCatalog) ctrl.Result { + var requeueAfter time.Duration + switch catalog.Spec.Source.Type { + case ocv1.SourceTypeImage: + if catalog.Spec.Source.Image != nil && catalog.Spec.Source.Image.PollIntervalMinutes != nil { + pollDuration := time.Duration(*catalog.Spec.Source.Image.PollIntervalMinutes) * time.Minute + jitteredDuration := wait.Jitter(pollDuration, requeueJitterMaxFactor) + requeueAfter = time.Until(lastSuccessfulPoll.Add(jitteredDuration)) + } + } + return ctrl.Result{RequeueAfter: requeueAfter} +} + +func clearUnknownConditions(status *ocv1.ClusterCatalogStatus) { + knownTypes := sets.New[string]( + ocv1.TypeServing, + ocv1.TypeProgressing, + ) + status.Conditions = slices.DeleteFunc(status.Conditions, func(cond metav1.Condition) bool { + return !knownTypes.Has(cond.Type) + }) +} + +func updateStatusProgressing(status *ocv1.ClusterCatalogStatus, generation int64, err error) { + progressingCond := metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: "Successfully unpacked and stored content from resolved source", + ObservedGeneration: generation, + } + + if err != nil { + progressingCond.Status = metav1.ConditionTrue + progressingCond.Reason = ocv1.ReasonRetrying + progressingCond.Message = err.Error() + } + + if errors.Is(err, reconcile.TerminalError(nil)) { + progressingCond.Status = metav1.ConditionFalse + progressingCond.Reason = ocv1.ReasonBlocked + } + + meta.SetStatusCondition(&status.Conditions, progressingCond) +} + +func updateStatusServing(status *ocv1.ClusterCatalogStatus, ref reference.Canonical, modTime time.Time, baseURL string, generation int64) { + status.ResolvedSource = &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: ref.String(), + }, + } + status.URLs = &ocv1.ClusterCatalogURLs{ + Base: baseURL, + } + status.LastUnpacked = ptr.To(metav1.NewTime(modTime.Truncate(time.Second))) + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + Message: "Serving desired content from resolved source", + ObservedGeneration: generation, + }) +} + +func updateStatusProgressingUserSpecifiedUnavailable(status *ocv1.ClusterCatalogStatus, generation int64) { + // Set Progressing condition to True with reason Succeeded + // since we have successfully progressed to the unavailable + // availability mode and are ready to progress to any future + // desired state. + progressingCond := metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: "Catalog availability mode is set to Unavailable", + ObservedGeneration: generation, + } + + // Set Serving condition to False with reason UserSpecifiedUnavailable + // so that users of this condition are aware that this catalog is + // intentionally not being served + servingCond := metav1.Condition{ + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUserSpecifiedUnavailable, + Message: "Catalog availability mode is set to Unavailable", + ObservedGeneration: generation, + } + + meta.SetStatusCondition(&status.Conditions, progressingCond) + meta.SetStatusCondition(&status.Conditions, servingCond) +} + +func updateStatusNotServing(status *ocv1.ClusterCatalogStatus, generation int64) { + status.ResolvedSource = nil + status.URLs = nil + status.LastUnpacked = nil + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUnavailable, + ObservedGeneration: generation, + }) +} + +func (r *ClusterCatalogReconciler) needsPoll(lastSuccessfulPoll time.Time, catalog *ocv1.ClusterCatalog) bool { + // If polling is disabled, we don't need to poll. + if catalog.Spec.Source.Image.PollIntervalMinutes == nil { + return false + } + + // Only poll if the next poll time is in the past. + nextPoll := lastSuccessfulPoll.Add(time.Duration(*catalog.Spec.Source.Image.PollIntervalMinutes) * time.Minute) + return nextPoll.Before(time.Now()) +} + +// Compare resources - ignoring status & metadata.finalizers +func checkForUnexpectedFieldChange(a, b ocv1.ClusterCatalog) bool { + a.Status, b.Status = ocv1.ClusterCatalogStatus{}, ocv1.ClusterCatalogStatus{} + a.Finalizers, b.Finalizers = []string{}, []string{} + return !equality.Semantic.DeepEqual(a, b) +} + +type finalizerFunc func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) + +func (f finalizerFunc) Finalize(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + return f(ctx, obj) +} + +func (r *ClusterCatalogReconciler) setupFinalizers() error { + f := crfinalizer.NewFinalizers() + err := f.Register(fbcDeletionFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + catalog, ok := obj.(*ocv1.ClusterCatalog) + if !ok { + panic("could not convert object to clusterCatalog") + } + err := r.deleteCatalogCache(ctx, catalog) + return crfinalizer.Result{StatusUpdated: true}, err + })) + if err != nil { + return err + } + r.finalizers = f + return nil +} + +func (r *ClusterCatalogReconciler) deleteStoredCatalog(catalogName string) { + r.storedCatalogsMu.Lock() + defer r.storedCatalogsMu.Unlock() + delete(r.storedCatalogs, catalogName) +} + +func (r *ClusterCatalogReconciler) deleteCatalogCache(ctx context.Context, catalog *ocv1.ClusterCatalog) error { + if err := r.Storage.Delete(catalog.Name); err != nil { + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), err) + return err + } + updateStatusNotServing(&catalog.Status, catalog.GetGeneration()) + if err := r.ImageCache.Delete(ctx, catalog.Name); err != nil { + updateStatusProgressing(&catalog.Status, catalog.GetGeneration(), err) + return err + } + r.deleteStoredCatalog(catalog.Name) + return nil +} diff --git a/internal/catalogd/controllers/core/clustercatalog_controller_test.go b/internal/catalogd/controllers/core/clustercatalog_controller_test.go new file mode 100644 index 0000000000..76efafa228 --- /dev/null +++ b/internal/catalogd/controllers/core/clustercatalog_controller_test.go @@ -0,0 +1,1163 @@ +package core + +import ( + "context" + "errors" + "fmt" + "io/fs" + "net/http" + "testing" + "testing/fstest" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.podman.io/image/v5/docker/reference" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/catalogd/storage" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" +) + +var _ storage.Instance = &MockStore{} + +type MockStore struct { + shouldError bool +} + +func (m MockStore) Store(_ context.Context, _ string, _ fs.FS) error { + if m.shouldError { + return errors.New("mockstore store error") + } + return nil +} + +func (m MockStore) Delete(_ string) error { + if m.shouldError { + return errors.New("mockstore delete error") + } + return nil +} + +func (m MockStore) BaseURL(_ string) string { + return "URL" +} + +func (m MockStore) StorageServerHandler() http.Handler { + panic("not needed") +} + +func (m MockStore) ContentExists(_ string) bool { + return true +} + +func TestCatalogdControllerReconcile(t *testing.T) { + for _, tt := range []struct { + name string + catalog *ocv1.ClusterCatalog + expectedError error + expectedCatalog *ocv1.ClusterCatalog + puller imageutil.Puller + cache imageutil.Cache + store storage.Instance + }{ + { + name: "invalid source type, returns error", + puller: &imageutil.MockPuller{}, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: "invalid", + }, + }, + }, + expectedError: reconcile.TerminalError(errors.New(`unknown source type "invalid"`)), + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: "invalid", + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonBlocked, + }, + }, + }, + }, + }, + { + name: "valid source type, unpack returns error, status updated to reflect error state and error is returned", + expectedError: fmt.Errorf("source catalog content: %w", fmt.Errorf("mockpuller error")), + puller: &imageutil.MockPuller{ + Error: errors.New("mockpuller error"), + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRetrying, + }, + }, + }, + }, + }, + { + name: "valid source type, unpack returns terminal error, status updated to reflect terminal error state(Blocked) and error is returned", + expectedError: fmt.Errorf("source catalog content: %w", reconcile.TerminalError(fmt.Errorf("mockpuller terminal error"))), + puller: &imageutil.MockPuller{ + Error: reconcile.TerminalError(errors.New("mockpuller terminal error")), + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonBlocked, + }, + }, + }, + }, + }, + { + name: "valid source type, unpack state == Unpacked, should reflect in status that it's progressing, and is serving", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + Ref: mustRef(t, "my.org/someimage@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + }, + }, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "my.org/someimage@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + }, + LastUnpacked: &metav1.Time{}, + }, + }, + }, + { + name: "valid source type, unpack state == Unpacked, storage fails, failure reflected in status and error returned", + expectedError: fmt.Errorf("error storing fbc: mockstore store error"), + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{ + shouldError: true, + }, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRetrying, + }, + }, + }, + }, + }, + { + name: "storage finalizer not set, storage finalizer gets set", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + }, + { + name: "storage finalizer set, catalog deletion timestamp is not zero (or nil), finalizer removed", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + LastUnpacked: &metav1.Time{}, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "", + }, + }, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUnavailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + }, + { + name: "storage finalizer set, catalog deletion timestamp is not zero (or nil), storage delete failed, error returned, finalizer not removed and catalog continues serving", + expectedError: fmt.Errorf("finalizer %q failed: %w", fbcDeletionFinalizer, fmt.Errorf("mockstore delete error")), + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{ + shouldError: true, + }, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonSucceeded, + }, + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRetrying, + }, + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + }, + }, + }, + }, + { + name: "storage finalizer set, catalog deletion timestamp is not zero (or nil), unpack cleanup failed, error returned, finalizer not removed but catalog stops serving", + expectedError: fmt.Errorf("finalizer %q failed: %w", fbcDeletionFinalizer, fmt.Errorf("mockcache delete error")), + puller: &imageutil.MockPuller{}, + cache: &imageutil.MockCache{DeleteErr: fmt.Errorf("mockcache delete error")}, + store: &MockStore{ + shouldError: false, + }, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonSucceeded, + }, + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2023, time.October, 10, 4, 19, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRetrying, + }, + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUnavailable, + }, + }, + }, + }, + }, + { + name: "catalog availability set to disabled, status.urls should get unset", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + LastUnpacked: &metav1.Time{}, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "", + }, + }, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUserSpecifiedUnavailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + }, + { + name: "catalog availability set to disabled, finalizer should get removed", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + LastUnpacked: &metav1.Time{}, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "", + }, + }, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUserSpecifiedUnavailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + }, + }, + }, + }, + }, + { + name: "after catalog availability set to enable, finalizer should be added", + puller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + }, + store: &MockStore{}, + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeAvailable, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "", + }, + }, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUnavailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUserSpecifiedUnavailable, + }, + }, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeAvailable, + }, + Status: ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: "", + }, + }, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeServing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUnavailable, + }, + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonUserSpecifiedUnavailable, + }, + }, + }, + }, + }, + { + name: "reconcile should be short-circuited if the clustercatalog has a deletion timestamp and all known finalizers have been removed", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{"finalizer"}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2025, 6, 10, 16, 43, 0, 0, time.UTC)}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeAvailable, + }, + }, + expectedCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "catalog", + Finalizers: []string{"finalizer"}, + DeletionTimestamp: &metav1.Time{Time: time.Date(2025, 6, 10, 16, 43, 0, 0, time.UTC)}}, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + AvailabilityMode: ocv1.AvailabilityModeAvailable, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + reconciler := &ClusterCatalogReconciler{ + Client: nil, + ImagePuller: tt.puller, + ImageCache: tt.cache, + Storage: tt.store, + storedCatalogs: map[string]storedCatalogData{}, + } + if reconciler.ImageCache == nil { + reconciler.ImageCache = &imageutil.MockCache{} + } + require.NoError(t, reconciler.setupFinalizers()) + ctx := context.Background() + + res, err := reconciler.reconcile(ctx, tt.catalog) + assert.Equal(t, ctrl.Result{}, res) + // errors are aggregated/wrapped + if tt.expectedError == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + assert.Equal(t, tt.expectedError.Error(), err.Error()) + } + diff := cmp.Diff(tt.expectedCatalog, tt.catalog, + cmpopts.IgnoreFields(metav1.Condition{}, "Message", "LastTransitionTime"), + cmpopts.SortSlices(func(a, b metav1.Condition) bool { return a.Type < b.Type })) + assert.Empty(t, diff, "comparing the expected Catalog") + }) + } +} + +func TestPollingRequeue(t *testing.T) { + for name, tc := range map[string]struct { + catalog *ocv1.ClusterCatalog + expectedRequeueAfter time.Duration + lastPollTime time.Time + }{ + "ClusterCatalog with tag based image ref without any poll interval specified, requeueAfter set to 0, ie polling disabled": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + }, + expectedRequeueAfter: time.Second * 0, + lastPollTime: time.Now(), + }, + "ClusterCatalog with tag based image ref with poll interval specified, just polled, requeueAfter set to wait.jitter(pollInterval)": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + PollIntervalMinutes: ptr.To(5), + }, + }, + }, + }, + expectedRequeueAfter: time.Minute * 5, + lastPollTime: time.Now(), + }, + "ClusterCatalog with tag based image ref with poll interval specified, last polled 2m ago, requeueAfter set to wait.jitter(pollInterval-2)": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + PollIntervalMinutes: ptr.To(5), + }, + }, + }, + }, + expectedRequeueAfter: time.Minute * 3, + lastPollTime: time.Now().Add(-2 * time.Minute), + }, + } { + t.Run(name, func(t *testing.T) { + ref := mustRef(t, "my.org/someimage@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + tc.catalog.Status = ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{ + {Type: ocv1.TypeServing, Status: metav1.ConditionTrue, Reason: ocv1.ReasonAvailable, Message: "Serving desired content from resolved source", LastTransitionTime: metav1.Now()}, + {Type: ocv1.TypeProgressing, Status: metav1.ConditionTrue, Reason: ocv1.ReasonSucceeded, Message: "Successfully unpacked and stored content from resolved source", LastTransitionTime: metav1.Now()}, + }, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{Ref: ref.String()}, + }, + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + LastUnpacked: ptr.To(metav1.NewTime(time.Now().Truncate(time.Second))), + } + reconciler := &ClusterCatalogReconciler{ + Client: nil, + ImagePuller: &imageutil.MockPuller{ + ImageFS: &fstest.MapFS{}, + Ref: ref, + }, + Storage: &MockStore{}, + storedCatalogs: map[string]storedCatalogData{ + tc.catalog.Name: { + ref: ref, + lastSuccessfulPoll: tc.lastPollTime, + lastUnpack: tc.catalog.Status.LastUnpacked.Time, + }, + }, + } + require.NoError(t, reconciler.setupFinalizers()) + res, _ := reconciler.reconcile(context.Background(), tc.catalog) + assert.InDelta(t, tc.expectedRequeueAfter, res.RequeueAfter, 2*requeueJitterMaxFactor*float64(tc.expectedRequeueAfter)) + }) + } +} + +func TestPollingReconcilerUnpack(t *testing.T) { + oldDigest := "a5d4f4467250074216eb1ba1c36e06a3ab797d81c431427fc2aca97ecaf4e9d8" + newDigest := "f42337e7b85a46d83c94694638e2312e10ca16a03542399a65ba783c94a32b63" + + successfulObservedGeneration := int64(2) + successfulRef := mustRef(t, "my.org/someimage@sha256:"+oldDigest) + successfulUnpackTime := time.Time{} + successfulUnpackStatus := func(mods ...func(status *ocv1.ClusterCatalogStatus)) ocv1.ClusterCatalogStatus { + s := ocv1.ClusterCatalogStatus{ + URLs: &ocv1.ClusterCatalogURLs{Base: "URL"}, + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: "Successfully unpacked and stored content from resolved source", + ObservedGeneration: successfulObservedGeneration, + }, + { + Type: ocv1.TypeServing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonAvailable, + Message: "Serving desired content from resolved source", + ObservedGeneration: successfulObservedGeneration, + }, + }, + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ResolvedImageSource{ + Ref: successfulRef.String(), + }, + }, + LastUnpacked: ptr.To(metav1.NewTime(successfulUnpackTime)), + } + for _, mod := range mods { + mod(&s) + } + return s + } + successfulStoredCatalogData := func(lastPoll time.Time) map[string]storedCatalogData { + return map[string]storedCatalogData{ + "test-catalog": { + observedGeneration: successfulObservedGeneration, + ref: successfulRef, + lastUnpack: successfulUnpackTime, + lastSuccessfulPoll: lastPoll, + }, + } + } + + for name, tc := range map[string]struct { + catalog *ocv1.ClusterCatalog + storedCatalogData map[string]storedCatalogData + expectedUnpackRun bool + }{ + "ClusterCatalog being resolved the first time, unpack should run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + PollIntervalMinutes: ptr.To(5), + }, + }, + }, + }, + expectedUnpackRun: true, + }, + "ClusterCatalog not being resolved the first time, no pollInterval mentioned, unpack should not run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 2, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + }, + }, + }, + Status: successfulUnpackStatus(), + }, + storedCatalogData: successfulStoredCatalogData(time.Now()), + expectedUnpackRun: false, + }, + "ClusterCatalog not being resolved the first time, pollInterval mentioned, \"now\" is before next expected poll time, unpack should not run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 2, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + PollIntervalMinutes: ptr.To(7), + }, + }, + }, + Status: successfulUnpackStatus(), + }, + storedCatalogData: successfulStoredCatalogData(time.Now()), + expectedUnpackRun: false, + }, + "ClusterCatalog not being resolved the first time, pollInterval mentioned, \"now\" is after next expected poll time, unpack should run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 2, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someimage:latest", + PollIntervalMinutes: ptr.To(3), + }, + }, + }, + Status: successfulUnpackStatus(), + }, + storedCatalogData: successfulStoredCatalogData(time.Now().Add(-5 * time.Minute)), + expectedUnpackRun: true, + }, + "ClusterCatalog not being resolved the first time, pollInterval mentioned, \"now\" is before next expected poll time, generation changed, unpack should run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 3, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someotherimage@sha256:" + newDigest, + PollIntervalMinutes: ptr.To(7), + }, + }, + }, + Status: successfulUnpackStatus(), + }, + storedCatalogData: successfulStoredCatalogData(time.Now()), + expectedUnpackRun: true, + }, + "ClusterCatalog not being resolved the first time, no stored catalog in cache, unpack should run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 3, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someotherimage@sha256:" + newDigest, + PollIntervalMinutes: ptr.To(7), + }, + }, + }, + Status: successfulUnpackStatus(), + }, + expectedUnpackRun: true, + }, + "ClusterCatalog not being resolved the first time, unexpected status, unpack should run": { + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Finalizers: []string{fbcDeletionFinalizer}, + Generation: 3, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: "my.org/someotherimage@sha256:" + newDigest, + PollIntervalMinutes: ptr.To(7), + }, + }, + }, + Status: successfulUnpackStatus(func(status *ocv1.ClusterCatalogStatus) { + meta.FindStatusCondition(status.Conditions, ocv1.TypeProgressing).Status = metav1.ConditionTrue + }), + }, + storedCatalogData: successfulStoredCatalogData(time.Now()), + expectedUnpackRun: true, + }, + } { + t.Run(name, func(t *testing.T) { + scd := tc.storedCatalogData + if scd == nil { + scd = map[string]storedCatalogData{} + } + reconciler := &ClusterCatalogReconciler{ + Client: nil, + ImagePuller: &imageutil.MockPuller{Error: errors.New("mockpuller error")}, + Storage: &MockStore{}, + storedCatalogs: scd, + } + require.NoError(t, reconciler.setupFinalizers()) + _, err := reconciler.reconcile(context.Background(), tc.catalog) + if tc.expectedUnpackRun { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func mustRef(t *testing.T, ref string) reference.Canonical { + t.Helper() + p, err := reference.Parse(ref) + if err != nil { + t.Fatal(err) + } + return p.(reference.Canonical) +} diff --git a/internal/catalogd/features/features.go b/internal/catalogd/features/features.go new file mode 100644 index 0000000000..298cbc8590 --- /dev/null +++ b/internal/catalogd/features/features.go @@ -0,0 +1,28 @@ +package features + +import ( + "github.com/go-logr/logr" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/component-base/featuregate" + + fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates" +) + +const ( + APIV1MetasHandler = featuregate.Feature("APIV1MetasHandler") +) + +var catalogdFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + APIV1MetasHandler: {Default: false, PreRelease: featuregate.Alpha}, +} + +var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() + +func init() { + utilruntime.Must(CatalogdFeatureGate.Add(catalogdFeatureGates)) +} + +// LogFeatureGateStates logs the state of all known feature gates for catalogd +func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) { + fgutil.LogFeatureGateStates(log, "catalogd feature gate status", fg, catalogdFeatureGates) +} diff --git a/internal/catalogd/garbagecollection/garbage_collector.go b/internal/catalogd/garbagecollection/garbage_collector.go new file mode 100644 index 0000000000..070d1ab1c3 --- /dev/null +++ b/internal/catalogd/garbagecollection/garbage_collector.go @@ -0,0 +1,94 @@ +package garbagecollection + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/metadata" + "sigs.k8s.io/controller-runtime/pkg/manager" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +var _ manager.Runnable = (*GarbageCollector)(nil) + +// GarbageCollector is an implementation of the manager.Runnable +// interface for running garbage collection on the Catalog content +// cache that is served by the catalogd HTTP server. It runs in a loop +// and will ensure that no cache entries exist for Catalog resources +// that no longer exist. This should only clean up cache entries that +// were missed by the handling of a DELETE event on a Catalog resource. +type GarbageCollector struct { + CachePath string + Logger logr.Logger + MetadataClient metadata.Interface + Interval time.Duration +} + +// Start will start the garbage collector. It will always run once on startup +// and loop until context is canceled after an initial garbage collection run. +// Garbage collection will run again every X amount of time, where X is the +// supplied garbage collection interval. +func (gc *GarbageCollector) Start(ctx context.Context) error { + // Run once on startup + removed, err := runGarbageCollection(ctx, gc.CachePath, gc.MetadataClient) + if err != nil { + gc.Logger.Error(err, "running garbage collection") + } + if len(removed) > 0 { + gc.Logger.Info("removed stale cache entries", "removed entries", removed) + } + + // Loop until context is canceled, running garbage collection + // at the configured interval + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(gc.Interval): + removed, err := runGarbageCollection(ctx, gc.CachePath, gc.MetadataClient) + if err != nil { + gc.Logger.Error(err, "running garbage collection") + } + if len(removed) > 0 { + gc.Logger.Info("removed stale cache entries", "removed entries", removed) + } + } + } +} + +func runGarbageCollection(ctx context.Context, cachePath string, metaClient metadata.Interface) ([]string, error) { + getter := metaClient.Resource(ocv1.GroupVersion.WithResource("clustercatalogs")) + metaList, err := getter.List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("error listing clustercatalogs: %w", err) + } + + expectedCatalogs := sets.New[string]() + for _, meta := range metaList.Items { + expectedCatalogs.Insert(meta.GetName()) + } + + cacheDirEntries, err := os.ReadDir(cachePath) + if err != nil { + return nil, fmt.Errorf("error reading cache directory: %w", err) + } + removed := []string{} + for _, cacheDirEntry := range cacheDirEntries { + if cacheDirEntry.IsDir() && expectedCatalogs.Has(cacheDirEntry.Name()) { + continue + } + if err := os.RemoveAll(filepath.Join(cachePath, cacheDirEntry.Name())); err != nil { + return nil, fmt.Errorf("error removing cache directory entry %q: %w ", cacheDirEntry.Name(), err) + } + + removed = append(removed, cacheDirEntry.Name()) + } + return removed, nil +} diff --git a/internal/catalogd/garbagecollection/garbage_collector_test.go b/internal/catalogd/garbagecollection/garbage_collector_test.go new file mode 100644 index 0000000000..dae428f6ee --- /dev/null +++ b/internal/catalogd/garbagecollection/garbage_collector_test.go @@ -0,0 +1,96 @@ +package garbagecollection + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/metadata/fake" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +func TestRunGarbageCollection(t *testing.T) { + for _, tt := range []struct { + name string + existCatalogs []*metav1.PartialObjectMetadata + notExistCatalogs []*metav1.PartialObjectMetadata + wantErr bool + }{ + { + name: "successful garbage collection", + existCatalogs: []*metav1.PartialObjectMetadata{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterCatalog", + APIVersion: ocv1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "one", + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterCatalog", + APIVersion: ocv1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "two", + }, + }, + }, + notExistCatalogs: []*metav1.PartialObjectMetadata{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterCatalog", + APIVersion: ocv1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "three", + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + cachePath := t.TempDir() + scheme := runtime.NewScheme() + require.NoError(t, metav1.AddMetaToScheme(scheme)) + + allCatalogs := append(tt.existCatalogs, tt.notExistCatalogs...) + for _, catalog := range allCatalogs { + require.NoError(t, os.MkdirAll(filepath.Join(cachePath, catalog.Name, "fakedigest"), os.ModePerm)) + } + + runtimeObjs := []runtime.Object{} + for _, catalog := range tt.existCatalogs { + runtimeObjs = append(runtimeObjs, catalog) + } + + metaClient := fake.NewSimpleMetadataClient(scheme, runtimeObjs...) + + _, err := runGarbageCollection(ctx, cachePath, metaClient) + if !tt.wantErr { + require.NoError(t, err) + entries, err := os.ReadDir(cachePath) + require.NoError(t, err) + assert.Len(t, entries, len(tt.existCatalogs)) + for _, catalog := range tt.existCatalogs { + assert.DirExists(t, filepath.Join(cachePath, catalog.Name)) + } + + for _, catalog := range tt.notExistCatalogs { + assert.NoDirExists(t, filepath.Join(cachePath, catalog.Name)) + } + } else { + assert.Error(t, err) + } + }) + } +} diff --git a/internal/catalogd/metrics/metrics.go b/internal/catalogd/metrics/metrics.go new file mode 100644 index 0000000000..c30aed5842 --- /dev/null +++ b/internal/catalogd/metrics/metrics.go @@ -0,0 +1,40 @@ +package metrics + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +const ( + RequestDurationMetricName = "catalogd_http_request_duration_seconds" +) + +// Sets up the necessary metrics for calculating the Apdex Score +// If using Grafana for visualization connected to a Prometheus data +// source that is scraping these metrics, you can create a panel that +// uses the following queries + expressions for calculating the Apdex Score where T = 0.5: +// Query A: sum(catalogd_http_request_duration_seconds_bucket{code!~"5..",le="0.5"}) +// Query B: sum(catalogd_http_request_duration_seconds_bucket{code!~"5..",le="2"}) +// Query C: sum(catalogd_http_request_duration_seconds_count) +// Expression for Apdex Score: ($A + (($B - $A) / 2)) / $C +var ( + RequestDurationMetric = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: RequestDurationMetricName, + Help: "Histogram of request duration in seconds", + // create a bucket for each 100 ms up to 1s and ensure it multiplied by 4 also exists. + // Include a 10s bucket to capture very long running requests. This allows us to easily + // calculate Apdex Scores up to a T of 1 second, but using various mathmatical formulas we + // should be able to estimate Apdex Scores up to a T of 2.5. Having a larger range of buckets + // will allow us to more easily calculate health indicators other than the Apdex Score. + Buckets: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2, 1.6, 2, 2.4, 2.8, 3.2, 3.6, 4, 10}, + }, + []string{"code"}, + ) +) + +func AddMetricsToHandler(handler http.Handler) http.Handler { + return promhttp.InstrumentHandlerDuration(RequestDurationMetric, handler) +} diff --git a/internal/catalogd/serverutil/serverutil.go b/internal/catalogd/serverutil/serverutil.go new file mode 100644 index 0000000000..143d4c8763 --- /dev/null +++ b/internal/catalogd/serverutil/serverutil.go @@ -0,0 +1,104 @@ +package serverutil + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/go-logr/logr" + "github.com/gorilla/handlers" + "github.com/klauspost/compress/gzhttp" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/manager" + + catalogdmetrics "github.com/operator-framework/operator-controller/internal/catalogd/metrics" + "github.com/operator-framework/operator-controller/internal/catalogd/storage" +) + +type CatalogServerConfig struct { + ExternalAddr string + CatalogAddr string + CertFile string + KeyFile string + LocalStorage storage.Instance +} + +func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig, tlsFileWatcher *certwatcher.CertWatcher) error { + listener, err := net.Listen("tcp", cfg.CatalogAddr) + if err != nil { + return fmt.Errorf("error creating catalog server listener: %w", err) + } + + if cfg.CertFile != "" && cfg.KeyFile != "" { + // Use the passed certificate watcher instead of creating a new one + config := &tls.Config{ + GetCertificate: tlsFileWatcher.GetCertificate, + MinVersion: tls.VersionTLS12, + } + listener = tls.NewListener(listener, config) + } + + shutdownTimeout := 30 * time.Second + catalogServer := manager.Server{ + Name: "catalogs", + OnlyServeWhenLeader: true, + Server: &http.Server{ + Addr: cfg.CatalogAddr, + Handler: storageServerHandlerWrapped(mgr.GetLogger().WithName("catalogd-http-server"), cfg), + ReadTimeout: 5 * time.Second, + // TODO: Revert this to 10 seconds if/when the API + // evolves to have significantly smaller responses + WriteTimeout: 5 * time.Minute, + }, + ShutdownTimeout: &shutdownTimeout, + Listener: listener, + } + + err = mgr.Add(&catalogServer) + if err != nil { + return fmt.Errorf("error adding catalog server to manager: %w", err) + } + + return nil +} + +func logrLoggingHandler(l logr.Logger, handler http.Handler) http.Handler { + return handlers.CustomLoggingHandler(nil, handler, func(_ io.Writer, params handlers.LogFormatterParams) { + // extract parameters used in apache common log format, but then log using `logr` to remain consistent + // with other loggers used in this codebase. + username := "-" + if params.URL.User != nil { + if name := params.URL.User.Username(); name != "" { + username = name + } + } + + host, _, err := net.SplitHostPort(params.Request.RemoteAddr) + if err != nil { + host = params.Request.RemoteAddr + } + + uri := params.Request.RequestURI + if params.Request.ProtoMajor == 2 && params.Request.Method == http.MethodConnect { + uri = params.Request.Host + } + if uri == "" { + uri = params.URL.RequestURI() + } + + l.Info("handled request", "host", host, "username", username, "method", params.Request.Method, "uri", uri, "protocol", params.Request.Proto, "status", params.StatusCode, "size", params.Size) + }) +} + +func storageServerHandlerWrapped(l logr.Logger, cfg CatalogServerConfig) http.Handler { + handler := cfg.LocalStorage.StorageServerHandler() + handler = gzhttp.GzipHandler(handler) + handler = catalogdmetrics.AddMetricsToHandler(handler) + + handler = logrLoggingHandler(l, handler) + return handler +} diff --git a/internal/catalogd/serverutil/serverutil_test.go b/internal/catalogd/serverutil/serverutil_test.go new file mode 100644 index 0000000000..183bf97f1c --- /dev/null +++ b/internal/catalogd/serverutil/serverutil_test.go @@ -0,0 +1,128 @@ +package serverutil + +import ( + "compress/gzip" + "context" + "io" + "io/fs" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/require" +) + +func TestStorageServerHandlerWrapped_Gzip(t *testing.T) { + var generatedJSON = func(size int) string { + return "{\"data\":\"" + strings.Repeat("test data ", size) + "\"}" + } + tests := []struct { + name string + acceptEncoding string + responseContent string + expectCompressed bool + expectedStatus int + }{ + { + name: "compresses large response when client accepts gzip", + acceptEncoding: "gzip", + responseContent: generatedJSON(1000), + expectCompressed: true, + expectedStatus: http.StatusOK, + }, + { + name: "does not compress small response even when client accepts gzip", + acceptEncoding: "gzip", + responseContent: `{"foo":"bar"}`, + expectCompressed: false, + expectedStatus: http.StatusOK, + }, + { + name: "does not compress when client doesn't accept gzip", + acceptEncoding: "", + responseContent: generatedJSON(1000), + expectCompressed: false, + expectedStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock storage instance that returns our test content + mockStorage := &mockStorageInstance{ + content: tt.responseContent, + } + + cfg := CatalogServerConfig{ + LocalStorage: mockStorage, + } + handler := storageServerHandlerWrapped(logr.Logger{}, cfg) + + // Create test request + req := httptest.NewRequest("GET", "/test", nil) + if tt.acceptEncoding != "" { + req.Header.Set("Accept-Encoding", tt.acceptEncoding) + } + + // Create response recorder + rec := httptest.NewRecorder() + + // Handle the request + handler.ServeHTTP(rec, req) + + // Check status code + require.Equal(t, tt.expectedStatus, rec.Code) + + // Check if response was compressed + wasCompressed := rec.Header().Get("Content-Encoding") == "gzip" + require.Equal(t, tt.expectCompressed, wasCompressed) + + // Get the response body + var responseBody []byte + if wasCompressed { + // Decompress the response + gzipReader, err := gzip.NewReader(rec.Body) + require.NoError(t, err) + responseBody, err = io.ReadAll(gzipReader) + require.NoError(t, err) + require.NoError(t, gzipReader.Close()) + } else { + responseBody = rec.Body.Bytes() + } + + // Verify the response content + require.Equal(t, tt.responseContent, string(responseBody)) + }) + } +} + +// mockStorageInstance implements storage.Instance interface for testing +type mockStorageInstance struct { + content string +} + +func (m *mockStorageInstance) StorageServerHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(m.content)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) +} + +func (m *mockStorageInstance) Store(ctx context.Context, catalogName string, fs fs.FS) error { + return nil +} + +func (m *mockStorageInstance) Delete(catalogName string) error { + return nil +} + +func (m *mockStorageInstance) ContentExists(catalog string) bool { + return true +} +func (m *mockStorageInstance) BaseURL(catalog string) string { + return "" +} diff --git a/internal/catalogd/storage/http_preconditions_check.go b/internal/catalogd/storage/http_preconditions_check.go new file mode 100644 index 0000000000..7fb5239b5a --- /dev/null +++ b/internal/catalogd/storage/http_preconditions_check.go @@ -0,0 +1,226 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Source: Originally from Go's net/http/fs.go +// https://cs.opensource.google/go/go/+/master:src/net/http/fs.go + +package storage + +import ( + "net/http" + "net/textproto" + "strings" + "time" +) + +type condResult int + +const ( + condNone condResult = iota + condTrue + condFalse +) + +// checkPreconditions evaluates request preconditions and reports whether a precondition +// resulted in sending StatusNotModified or StatusPreconditionFailed. +func checkPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) bool { + // This function carefully follows RFC 7232 section 6. + ch := checkIfMatch(r) + if ch == condNone { + ch = checkIfUnmodifiedSince(r, modtime) + } + if ch == condFalse { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + switch checkIfNoneMatch(r) { + case condFalse: + if r.Method == "GET" || r.Method == "HEAD" { + writeNotModified(w) + return true + } else { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + case condNone: + if checkIfModifiedSince(r, w, modtime) == condFalse { + writeNotModified(w) + return true + } + } + return false +} + +func checkIfModifiedSince(r *http.Request, w http.ResponseWriter, modtime time.Time) condResult { + ims := r.Header.Get("If-Modified-Since") + if ims == "" || isZeroTime(modtime) { + return condTrue + } + t, err := parseTime(ims) + if err != nil { + httpError(w, err) + return condNone + } + // The Last-Modified header truncates sub-second precision so + // the modtime needs to be truncated too. + modtime = modtime.Truncate(time.Second) + if modtime.Compare(t) <= 0 { + return condFalse + } + return condTrue +} + +func checkIfUnmodifiedSince(r *http.Request, modtime time.Time) condResult { + ius := r.Header.Get("If-Unmodified-Since") + if ius == "" || isZeroTime(modtime) { + return condNone + } + t, err := parseTime(ius) + if err != nil { + return condNone + } + + // The Last-Modified header truncates sub-second precision so + // the modtime needs to be truncated too. + modtime = modtime.Truncate(time.Second) + if ret := modtime.Compare(t); ret <= 0 { + return condTrue + } + return condFalse +} + +// timeFormat is the time format to use when generating times in HTTP +// headers. It is like [time.RFC1123] but hard-codes GMT as the time +// zone. The time being formatted must be in UTC for Format to +// generate the correct format. +// +// For parsing this time format, see [ParseTime]. +const timeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" + +var ( + unixEpochTime = time.Unix(0, 0) + timeFormats = []string{ + timeFormat, + time.RFC850, + time.ANSIC, + } +) + +// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0). +func isZeroTime(t time.Time) bool { + return t.IsZero() || t.Equal(unixEpochTime) +} + +func writeNotModified(w http.ResponseWriter) { + // RFC 7232 section 4.1: + // a sender SHOULD NOT generate representation metadata other than the + // above listed fields unless said metadata exists for the purpose of + // guiding cache updates (e.g., Last-Modified might be useful if the + // response does not have an ETag field). + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + delete(h, "Content-Encoding") + if h.Get("Etag") != "" { + delete(h, "Last-Modified") + } + w.WriteHeader(http.StatusNotModified) +} + +func checkIfNoneMatch(r *http.Request) condResult { + inm := r.Header.Get("If-None-Match") + if inm == "" { + return condNone + } + buf := inm + for { + buf = textproto.TrimString(buf) + if len(buf) == 0 { + break + } + if buf[0] == ',' { + buf = buf[1:] + continue + } + if buf[0] == '*' { + return condFalse + } + etag, remain := scanETag(buf) + if etag == "" { + break + } + buf = remain + } + return condTrue +} + +// parseTime parses a time header (such as the Date: header), +// trying each of the three formats allowed by HTTP/1.1: +// [TimeFormat], [time.RFC850], and [time.ANSIC]. +// nolint:nonamedreturns +func parseTime(text string) (t time.Time, err error) { + for _, layout := range timeFormats { + t, err = time.Parse(layout, text) + if err == nil { + return + } + } + return +} + +func checkIfMatch(r *http.Request) condResult { + im := r.Header.Get("If-Match") + if im == "" { + return condNone + } + for { + im = textproto.TrimString(im) + if len(im) == 0 { + break + } + if im[0] == ',' { + im = im[1:] + continue + } + if im[0] == '*' { + return condTrue + } + etag, remain := scanETag(im) + if etag == "" { + break + } + im = remain + } + + return condFalse +} + +// scanETag determines if a syntactically valid ETag is present at s. If so, +// the ETag and remaining text after consuming ETag is returned. Otherwise, +// it returns "", "". +// nolint:nonamedreturns +func scanETag(s string) (etag string, remain string) { + s = textproto.TrimString(s) + start := 0 + if strings.HasPrefix(s, "W/") { + start = 2 + } + if len(s[start:]) < 2 || s[start] != '"' { + return "", "" + } + // ETag is either W/"text" or "text". + // See RFC 7232 2.3. + for i := start + 1; i < len(s); i++ { + c := s[i] + switch { + // Character values allowed in ETags. + case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80: + case c == '"': + return s[:i+1], s[i+1:] + default: + return "", "" + } + } + return "", "" +} diff --git a/internal/catalogd/storage/index.go b/internal/catalogd/storage/index.go new file mode 100644 index 0000000000..510e23ff05 --- /dev/null +++ b/internal/catalogd/storage/index.go @@ -0,0 +1,119 @@ +package storage + +import ( + "cmp" + "encoding/json" + "fmt" + "io" + "slices" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/operator-framework/operator-registry/alpha/declcfg" +) + +// index is an index of sections of an FBC file used to lookup FBC blobs that +// match any combination of their schema, package, and name fields. + +// This index strikes a balance between space and performance. It indexes each field +// separately, and performs logical set intersections at lookup time in order to implement +// a multi-parameter query. +// +// Note: it is permissible to change the indexing algorithm later if it is necessary to +// tune the space / performance tradeoff. However care should be taken to ensure +// that the actual content returned by the index remains identical, as users of the index +// may be sensitive to differences introduced by index algorithm changes (e.g. if the +// order of the returned sections changes). +type index struct { + BySchema map[string][]section `json:"by_schema"` + ByPackage map[string][]section `json:"by_package"` + ByName map[string][]section `json:"by_name"` +} + +// A section is the byte offset and length of an FBC blob within the file. +type section struct { + offset int64 + length int64 +} + +func (s *section) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`[%d,%d]`, s.offset, s.length)), nil +} + +func (s *section) UnmarshalJSON(b []byte) error { + vals := [2]int64{} + if err := json.Unmarshal(b, &vals); err != nil { + return err + } + s.offset = vals[0] + s.length = vals[1] + return nil +} + +func (i index) Get(r io.ReaderAt, schema, packageName, name string) io.Reader { + sectionSet := i.getSectionSet(schema, packageName, name) + + sections := sectionSet.UnsortedList() + slices.SortFunc(sections, func(a, b section) int { + return cmp.Compare(a.offset, b.offset) + }) + + srs := make([]io.Reader, 0, len(sections)) + for _, s := range sections { + sr := io.NewSectionReader(r, s.offset, s.length) + srs = append(srs, sr) + } + return io.MultiReader(srs...) +} + +func (i *index) getSectionSet(schema, packageName, name string) sets.Set[section] { + // Initialize with all sections if no schema specified, otherwise use schema sections + sectionSet := sets.New[section]() + if schema == "" { + for _, s := range i.BySchema { + sectionSet.Insert(s...) + } + } else { + sectionSet = sets.New[section](i.BySchema[schema]...) + } + + // Filter by package name if specified + if packageName != "" { + packageSections := sets.New[section](i.ByPackage[packageName]...) + sectionSet = sectionSet.Intersection(packageSections) + } + + // Filter by name if specified + if name != "" { + nameSections := sets.New[section](i.ByName[name]...) + sectionSet = sectionSet.Intersection(nameSections) + } + + return sectionSet +} + +func newIndex(metasChan <-chan *declcfg.Meta) *index { + idx := &index{ + BySchema: make(map[string][]section), + ByPackage: make(map[string][]section), + ByName: make(map[string][]section), + } + offset := int64(0) + for meta := range metasChan { + start := offset + length := int64(len(meta.Blob)) + offset += length + + s := section{offset: start, length: length} + if meta.Schema != "" { + idx.BySchema[meta.Schema] = append(idx.BySchema[meta.Schema], s) + } + if meta.Package != "" { + idx.ByPackage[meta.Package] = append(idx.ByPackage[meta.Package], s) + } + if meta.Name != "" { + idx.ByName[meta.Name] = append(idx.ByName[meta.Name], s) + } + } + return idx +} diff --git a/internal/catalogd/storage/index_test.go b/internal/catalogd/storage/index_test.go new file mode 100644 index 0000000000..66dee0c133 --- /dev/null +++ b/internal/catalogd/storage/index_test.go @@ -0,0 +1,285 @@ +package storage + +import ( + "bytes" + "encoding/json" + "io" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-registry/alpha/declcfg" +) + +func TestIndexCreation(t *testing.T) { + // Create test Meta objects + metas := []*declcfg.Meta{ + { + Schema: "olm.package", + Package: "test", + Name: "test-package", + Blob: []byte(`{"test": "data1"}`), + }, + { + Schema: "olm.bundle", + Package: "test", + Name: "test-bundle", + Blob: []byte(`{"test": "data2"}`), + }, + } + + // Create channel and feed Metas + metasChan := make(chan *declcfg.Meta, len(metas)) + for _, meta := range metas { + metasChan <- meta + } + close(metasChan) + + // Create index + idx := newIndex(metasChan) + + // Verify schema index + require.Len(t, idx.BySchema, 2, "Expected 2 schema entries, got %d", len(idx.BySchema)) + require.Len(t, idx.BySchema["olm.package"], 1, "Expected 1 olm.package entry, got %d", len(idx.BySchema["olm.package"])) + require.Len(t, idx.BySchema["olm.bundle"], 1, "Expected 1 olm.bundle entry, got %d", len(idx.BySchema["olm.bundle"])) + + // Verify package index + require.Len(t, idx.ByPackage["test"], 2, "Expected 2 package entries, got %d", len(idx.ByPackage)) + + // Verify name index + require.Len(t, idx.ByName["test-package"], 1, "Expected 1 entry for name 'test-package', got %d", len(idx.ByName["test-package"])) + require.Len(t, idx.ByName["test-bundle"], 1, "Expected 1 entry for name 'test-bundle', got %d", len(idx.ByName["test-bundle"])) +} + +func TestIndexGet(t *testing.T) { + // Test data structure that represents a catalog + metas := []*declcfg.Meta{ + { + // Package definition + Schema: "olm.package", + Name: "test-package", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.package", + "name": "test-package", + "defaultChannel": "stable-v6.x", + }), + }, + { + // First channel (stable-5.x) + Schema: "olm.channel", + Package: "test-package", + Name: "stable-5.x", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.channel", + "name": "stable-5.x", + "package": "test-package", + "entries": []map[string]interface{}{ + {"name": "test-bunble.v5.0.3"}, + {"name": "test-bundle.v5.0.4", "replaces": "test-bundle.v5.0.3"}, + }, + }), + }, + { + // Second channel (stable-v6.x) + Schema: "olm.channel", + Package: "test-package", + Name: "stable-v6.x", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.channel", + "name": "stable-v6.x", + "package": "test-package", + "entries": []map[string]interface{}{ + {"name": "test-bundle.v6.0.0", "skipRange": "<6.0.0"}, + }, + }), + }, + { + // Bundle v5.0.3 + Schema: "olm.bundle", + Package: "test-package", + Name: "test-bundle.v5.0.3", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.bundle", + "name": "test-bundle.v5.0.3", + "package": "test-package", + "image": "test-image@sha256:a5d4f", + "properties": []map[string]interface{}{ + { + "type": "olm.package", + "value": map[string]interface{}{ + "packageName": "test-package", + "version": "5.0.3", + }, + }, + }, + }), + }, + { + // Bundle v5.0.4 + Schema: "olm.bundle", + Package: "test-package", + Name: "test-bundle.v5.0.4", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.bundle", + "name": "test-bundle.v5.0.4", + "package": "test-package", + "image": "test-image@sha256:f4233", + "properties": []map[string]interface{}{ + { + "type": "olm.package", + "value": map[string]interface{}{ + "packageName": "test-package", + "version": "5.0.4", + }, + }, + }, + }), + }, + { + // Bundle v6.0.0 + Schema: "olm.bundle", + Package: "test-package", + Name: "test-bundle.v6.0.0", + Blob: createBlob(t, map[string]interface{}{ + "schema": "olm.bundle", + "name": "test-bundle.v6.0.0", + "package": "test-package", + "image": "test-image@sha256:d3016b", + "properties": []map[string]interface{}{ + { + "type": "olm.package", + "value": map[string]interface{}{ + "packageName": "test-package", + "version": "6.0.0", + }, + }, + }, + }), + }, + } + + // Create and populate the index + metasChan := make(chan *declcfg.Meta, len(metas)) + for _, meta := range metas { + metasChan <- meta + } + close(metasChan) + + idx := newIndex(metasChan) + + // Create a reader from the metas + var combinedBlob bytes.Buffer + for _, meta := range metas { + combinedBlob.Write(meta.Blob) + } + fullData := bytes.NewReader(combinedBlob.Bytes()) + + tests := []struct { + name string + schema string + packageName string + blobName string + wantCount int + validate func(t *testing.T, entry map[string]interface{}) + }{ + { + name: "filter by schema - olm.package", + schema: "olm.package", + wantCount: 1, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["schema"] != "olm.package" { + t.Errorf("Expected olm.package schema blob got %v", entry["schema"]) + } + }, + }, + { + name: "filter by schema - olm.channel", + schema: "olm.channel", + wantCount: 2, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["schema"] != "olm.channel" { + t.Errorf("Expected olm.channel schema blob got %v", entry["schema"]) + } + }, + }, + { + name: "filter by schema - olm.bundle", + schema: "olm.bundle", + wantCount: 3, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["schema"] != "olm.bundle" { + t.Errorf("Expected olm.bundle schema blob got %v", entry["schema"]) + } + }, + }, + { + name: "filter by package", + packageName: "test-package", + wantCount: 5, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["package"] != "test-package" { + t.Errorf("Expected blobs with package name test-package, got blob with package name %v", entry["package"]) + } + }, + }, + { + name: "filter by specific bundle name", + blobName: "test-bundle.v5.0.3", + wantCount: 1, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["schema"] != "olm.bundle" && entry["name"] != "test-bundle.v5.0.3" { + t.Errorf("Expected blob with schema=olm.bundle and name=test-bundle.v5.0.3, got %v", entry) + } + }, + }, + { + name: "filter by schema and package", + schema: "olm.bundle", + packageName: "test-package", + wantCount: 3, + validate: func(t *testing.T, entry map[string]interface{}) { + if entry["schema"] != "olm.bundle" && entry["package"] != "test-package" { + t.Errorf("Expected blob with schema=olm.bundle and package=test-package, got %v", entry) + } + }, + }, + { + name: "no matches", + schema: "non.existent", + packageName: "not-found", + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := idx.Get(fullData, tt.schema, tt.packageName, tt.blobName) + content, err := io.ReadAll(reader) + require.NoError(t, err, "Failed to read content: %v", err) + + var count int + decoder := json.NewDecoder(bytes.NewReader(content)) + for decoder.More() { + var entry map[string]interface{} + err := decoder.Decode(&entry) + require.NoError(t, err, "Failed to decode result: %v", err) + count++ + + if tt.validate != nil { + tt.validate(t, entry) + } + } + + require.Equal(t, tt.wantCount, count, "Got %d entries, want %d", count, tt.wantCount) + }) + } +} + +// createBlob is a helper function that creates a JSON blob with a trailing newline +func createBlob(t *testing.T, data map[string]interface{}) []byte { + blob, err := json.Marshal(data) + if err != nil { + t.Fatalf("Failed to create blob: %v", err) + } + return append(blob, '\n') +} diff --git a/internal/catalogd/storage/localdir.go b/internal/catalogd/storage/localdir.go new file mode 100644 index 0000000000..44ef65c581 --- /dev/null +++ b/internal/catalogd/storage/localdir.go @@ -0,0 +1,330 @@ +package storage + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "net/url" + "os" + "path/filepath" + "sync" + + "golang.org/x/sync/errgroup" + "golang.org/x/sync/singleflight" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/operator-framework/operator-registry/alpha/declcfg" +) + +// LocalDirV1 is a storage Instance. When Storing a new FBC contained in +// fs.FS, the content is first written to a temporary file, after which +// it is copied to its final destination in RootDir/.jsonl. This is +// done so that clients accessing the content stored in RootDir/.json1 +// have an atomic view of the content for a catalog. +type LocalDirV1 struct { + RootDir string + RootURL *url.URL + EnableMetasHandler bool + + m sync.RWMutex + // this singleflight Group is used in `getIndex()`` to handle concurrent HTTP requests + // optimally. With the use of this slightflight group, the index is loaded from disk + // once per concurrent group of HTTP requests being handled by the metas handler. + // The single flight instance gives us a way to load the index from disk exactly once + // per concurrent group of callers, and then let every concurrent caller have access to + // the loaded index. This avoids lots of unnecessary open/decode/close cycles when concurrent + // requests are being handled, which improves overall performance and decreases response latency. + sf singleflight.Group +} + +var ( + _ Instance = (*LocalDirV1)(nil) + errInvalidParams = errors.New("invalid parameters") +) + +func (s *LocalDirV1) Store(ctx context.Context, catalog string, fsys fs.FS) error { + s.m.Lock() + defer s.m.Unlock() + + if err := os.MkdirAll(s.RootDir, 0700); err != nil { + return err + } + tmpCatalogDir, err := os.MkdirTemp(s.RootDir, fmt.Sprintf(".%s-*", catalog)) + if err != nil { + return err + } + defer os.RemoveAll(tmpCatalogDir) + + storeMetaFuncs := []storeMetasFunc{storeCatalogData} + if s.EnableMetasHandler { + storeMetaFuncs = append(storeMetaFuncs, storeIndexData) + } + + eg, egCtx := errgroup.WithContext(ctx) + metaChans := []chan *declcfg.Meta{} + + for range storeMetaFuncs { + metaChans = append(metaChans, make(chan *declcfg.Meta, 1)) + } + for i, f := range storeMetaFuncs { + eg.Go(func() error { + return f(tmpCatalogDir, metaChans[i]) + }) + } + err = declcfg.WalkMetasFS(egCtx, fsys, func(path string, meta *declcfg.Meta, err error) error { + if err != nil { + return err + } + for _, ch := range metaChans { + select { + case ch <- meta: + case <-egCtx.Done(): + return egCtx.Err() + } + } + return nil + }, declcfg.WithConcurrency(1)) + for _, ch := range metaChans { + close(ch) + } + if err != nil { + return fmt.Errorf("error walking FBC root: %w", err) + } + + if err := eg.Wait(); err != nil { + return err + } + + catalogDir := s.catalogDir(catalog) + return errors.Join( + os.RemoveAll(catalogDir), + os.Rename(tmpCatalogDir, catalogDir), + ) +} + +func (s *LocalDirV1) Delete(catalog string) error { + s.m.Lock() + defer s.m.Unlock() + + return os.RemoveAll(s.catalogDir(catalog)) +} + +func (s *LocalDirV1) ContentExists(catalog string) bool { + s.m.RLock() + defer s.m.RUnlock() + + catalogFileStat, err := os.Stat(catalogFilePath(s.catalogDir(catalog))) + if err != nil { + return false + } + if !catalogFileStat.Mode().IsRegular() { + // path is not valid content + return false + } + + if s.EnableMetasHandler { + indexFileStat, err := os.Stat(catalogIndexFilePath(s.catalogDir(catalog))) + if err != nil { + return false + } + if !indexFileStat.Mode().IsRegular() { + return false + } + } + return true +} + +func (s *LocalDirV1) catalogDir(catalog string) string { + return filepath.Join(s.RootDir, catalog) +} + +func catalogFilePath(catalogDir string) string { + return filepath.Join(catalogDir, "catalog.jsonl") +} + +func catalogIndexFilePath(catalogDir string) string { + return filepath.Join(catalogDir, "index.json") +} + +type storeMetasFunc func(catalogDir string, metaChan <-chan *declcfg.Meta) error + +func storeCatalogData(catalogDir string, metas <-chan *declcfg.Meta) error { + f, err := os.Create(catalogFilePath(catalogDir)) + if err != nil { + return err + } + defer f.Close() + + for m := range metas { + if _, err := f.Write(m.Blob); err != nil { + return err + } + } + return nil +} + +func storeIndexData(catalogDir string, metas <-chan *declcfg.Meta) error { + idx := newIndex(metas) + + f, err := os.Create(catalogIndexFilePath(catalogDir)) + if err != nil { + return err + } + defer f.Close() + + enc := json.NewEncoder(f) + enc.SetEscapeHTML(false) + return enc.Encode(idx) +} + +func (s *LocalDirV1) BaseURL(catalog string) string { + return s.RootURL.JoinPath(catalog).String() +} + +func (s *LocalDirV1) StorageServerHandler() http.Handler { + mux := http.NewServeMux() + + mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "all").Path, s.handleV1All) + if s.EnableMetasHandler { + mux.HandleFunc(s.RootURL.JoinPath("{catalog}", "api", "v1", "metas").Path, s.handleV1Metas) + } + allowedMethodsHandler := func(next http.Handler, allowedMethods ...string) http.Handler { + allowedMethodSet := sets.New[string](allowedMethods...) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !allowedMethodSet.Has(r.Method) { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + next.ServeHTTP(w, r) + }) + } + return allowedMethodsHandler(mux, http.MethodGet, http.MethodHead) +} + +func (s *LocalDirV1) handleV1All(w http.ResponseWriter, r *http.Request) { + s.m.RLock() + defer s.m.RUnlock() + + catalog := r.PathValue("catalog") + catalogFile, catalogStat, err := s.catalogData(catalog) + if err != nil { + httpError(w, err) + return + } + w.Header().Add("Content-Type", "application/jsonl") + http.ServeContent(w, r, "", catalogStat.ModTime(), catalogFile) +} + +func (s *LocalDirV1) handleV1Metas(w http.ResponseWriter, r *http.Request) { + s.m.RLock() + defer s.m.RUnlock() + + // Check for unexpected query parameters + expectedParams := map[string]bool{ + "schema": true, + "package": true, + "name": true, + } + + for param := range r.URL.Query() { + if !expectedParams[param] { + httpError(w, errInvalidParams) + return + } + } + + catalog := r.PathValue("catalog") + catalogFile, catalogStat, err := s.catalogData(catalog) + if err != nil { + httpError(w, err) + return + } + defer catalogFile.Close() + + w.Header().Set("Last-Modified", catalogStat.ModTime().UTC().Format(timeFormat)) + done := checkPreconditions(w, r, catalogStat.ModTime()) + if done { + return + } + + schema := r.URL.Query().Get("schema") + pkg := r.URL.Query().Get("package") + name := r.URL.Query().Get("name") + + if schema == "" && pkg == "" && name == "" { + // If no parameters are provided, return the entire catalog (this is the same as /api/v1/all) + serveJSONLines(w, r, catalogFile) + return + } + idx, err := s.getIndex(catalog) + if err != nil { + httpError(w, err) + return + } + indexReader := idx.Get(catalogFile, schema, pkg, name) + serveJSONLines(w, r, indexReader) +} + +func (s *LocalDirV1) catalogData(catalog string) (*os.File, os.FileInfo, error) { + catalogFile, err := os.Open(catalogFilePath(s.catalogDir(catalog))) + if err != nil { + return nil, nil, err + } + catalogFileStat, err := catalogFile.Stat() + if err != nil { + return nil, nil, err + } + return catalogFile, catalogFileStat, nil +} + +func httpError(w http.ResponseWriter, err error) { + var code int + switch { + case errors.Is(err, fs.ErrNotExist): + code = http.StatusNotFound + case errors.Is(err, fs.ErrPermission): + code = http.StatusForbidden + case errors.Is(err, errInvalidParams): + code = http.StatusBadRequest + default: + code = http.StatusInternalServerError + } + http.Error(w, fmt.Sprintf("%d %s", code, http.StatusText(code)), code) +} + +func serveJSONLines(w http.ResponseWriter, r *http.Request, rs io.Reader) { + w.Header().Add("Content-Type", "application/jsonl") + // Copy the content of the reader to the response writer + // only if it's a Get request + if r.Method == http.MethodHead { + return + } + _, err := io.Copy(w, rs) + if err != nil { + httpError(w, err) + return + } +} + +func (s *LocalDirV1) getIndex(catalog string) (*index, error) { + idx, err, _ := s.sf.Do(catalog, func() (interface{}, error) { + indexFile, err := os.Open(catalogIndexFilePath(s.catalogDir(catalog))) + if err != nil { + return nil, err + } + defer indexFile.Close() + var idx index + if err := json.NewDecoder(indexFile).Decode(&idx); err != nil { + return nil, err + } + return &idx, nil + }) + if err != nil { + return nil, err + } + return idx.(*index), nil +} diff --git a/internal/catalogd/storage/localdir_test.go b/internal/catalogd/storage/localdir_test.go new file mode 100644 index 0000000000..72aafba1c7 --- /dev/null +++ b/internal/catalogd/storage/localdir_test.go @@ -0,0 +1,636 @@ +package storage + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "sync" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-registry/alpha/declcfg" +) + +const urlPrefix = "/catalogs/" + +func TestLocalDirStoraget(t *testing.T) { + tests := []struct { + name string + setup func(*testing.T) (*LocalDirV1, fs.FS) + test func(*testing.T, *LocalDirV1, fs.FS) + cleanup func(*testing.T, *LocalDirV1) + }{ + { + name: "store and retrieve catalog content", + setup: func(t *testing.T) (*LocalDirV1, fs.FS) { + s := &LocalDirV1{ + RootDir: t.TempDir(), + RootURL: &url.URL{Scheme: "http", Host: "test-addr", Path: urlPrefix}, + } + return s, createTestFS(t) + }, + test: func(t *testing.T, s *LocalDirV1, fsys fs.FS) { + const catalog = "test-catalog" + + // Initially content should not exist + if s.ContentExists(catalog) { + t.Fatal("content should not exist before store") + } + + // Store the content + if err := s.Store(context.Background(), catalog, fsys); err != nil { + t.Fatal(err) + } + + // Verify content exists after store + if !s.ContentExists(catalog) { + t.Fatal("content should exist after store") + } + + // Delete the content + if err := s.Delete(catalog); err != nil { + t.Fatal(err) + } + + // Verify content no longer exists + if s.ContentExists(catalog) { + t.Fatal("content should not exist after delete") + } + }, + }, + { + name: "storing with metas handler enabled should create indices", + setup: func(t *testing.T) (*LocalDirV1, fs.FS) { + s := &LocalDirV1{ + RootDir: t.TempDir(), + EnableMetasHandler: true, + } + return s, createTestFS(t) + }, + test: func(t *testing.T, s *LocalDirV1, fsys fs.FS) { + err := s.Store(context.Background(), "test-catalog", fsys) + if err != nil { + t.Fatal(err) + } + + if !s.ContentExists("test-catalog") { + t.Error("content should exist after store") + } + + // Verify index file was created + indexPath := catalogIndexFilePath(s.catalogDir("test-catalog")) + if _, err := os.Stat(indexPath); err != nil { + t.Errorf("index file should exist: %v", err) + } + }, + }, + { + name: "concurrent reads during write should not cause data race", + setup: func(t *testing.T) (*LocalDirV1, fs.FS) { + dir := t.TempDir() + s := &LocalDirV1{RootDir: dir} + return s, createTestFS(t) + }, + test: func(t *testing.T, s *LocalDirV1, fsys fs.FS) { + const catalog = "test-catalog" + var wg sync.WaitGroup + + // Start multiple concurrent readers + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 100; j++ { + s.ContentExists(catalog) + } + }() + } + + // Write while readers are active + err := s.Store(context.Background(), catalog, fsys) + if err != nil { + t.Fatal(err) + } + + wg.Wait() + }, + }, + { + name: "delete nonexistent catalog", + setup: func(t *testing.T) (*LocalDirV1, fs.FS) { + return &LocalDirV1{RootDir: t.TempDir()}, nil + }, + test: func(t *testing.T, s *LocalDirV1, _ fs.FS) { + err := s.Delete("nonexistent") + if err != nil { + t.Errorf("expected no error deleting nonexistent catalog, got: %v", err) + } + }, + }, + { + name: "store with invalid permissions", + setup: func(t *testing.T) (*LocalDirV1, fs.FS) { + dir := t.TempDir() + // Set directory permissions to deny access + if err := os.Chmod(dir, 0000); err != nil { + t.Fatal(err) + } + return &LocalDirV1{RootDir: dir}, createTestFS(t) + }, + test: func(t *testing.T, s *LocalDirV1, fsys fs.FS) { + err := s.Store(context.Background(), "test-catalog", fsys) + if !errors.Is(err, fs.ErrPermission) { + t.Errorf("expected permission error, got: %v", err) + } + }, + cleanup: func(t *testing.T, s *LocalDirV1) { + // Restore permissions so cleanup can succeed + require.NoError(t, os.Chmod(s.RootDir, 0700)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, fsys := tt.setup(t) + tt.test(t, s, fsys) + if tt.cleanup != nil { + tt.cleanup(t, s) + } + }) + } +} + +func TestLocalDirServerHandler(t *testing.T) { + store := &LocalDirV1{RootDir: t.TempDir(), RootURL: &url.URL{Path: urlPrefix}} + if store.Store(context.Background(), "test-catalog", createTestFS(t)) != nil { + t.Fatal("failed to store test catalog and start server") + } + + testServer := httptest.NewServer(store.StorageServerHandler()) + defer testServer.Close() + + for _, tc := range []struct { + name string + URLPath string + expectedStatusCode int + expectedContent string + }{ + { + name: "Server returns 404 when root URL is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "", + }, + { + name: "Server returns 404 when path '/' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/", + }, + { + name: "Server returns 404 when path '/catalogs/' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/", + }, + { + name: "Server returns 404 when path '/catalogs//' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/test-catalog/", + }, + { + name: "Server returns 404 when path '/catalogs//api/' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/test-catalog/api/", + }, + { + name: "Serer return 404 when path '/catalogs//api/v1' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/test-catalog/api/v1c", + }, + { + name: "Server return 404 when path '/catalogs//non-existent.txt' is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/test-catalog/non-existent.txt", + }, + { + name: "Server returns 404 when path '/catalogs/.jsonl' is queried even if the file exists, since we don't serve the filesystem, and serve an API instead", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 page not found", + URLPath: "/catalogs/test-catalog.jsonl", + }, + { + name: "Server returns 404 when non-existent catalog is queried", + expectedStatusCode: http.StatusNotFound, + expectedContent: "404 Not Found", + URLPath: "/catalogs/non-existent-catalog/api/v1/all", + }, + { + name: "Server returns 200 with json-lines payload when path '/catalogs//api/v1/all' is queried, when catalog exists", + expectedStatusCode: http.StatusOK, + expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}` + "\n" + + `{"defaultChannel":"preview_test","name":"webhook_operator_test","schema":"olm.package"}` + "\n" + + `{"entries":[{"name":"bundle.v0.0.1"}],"name":"preview_test","package":"webhook_operator_test","schema":"olm.channel"}`, + URLPath: "/catalogs/test-catalog/api/v1/all", + }, + } { + t.Run(tc.name, func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", testServer.URL, tc.URLPath), nil) + require.NoError(t, err) + req.Header.Set("Accept-Encoding", "gzip") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + require.Equal(t, tc.expectedStatusCode, resp.StatusCode) + if resp.StatusCode == http.StatusOK { + assert.Equal(t, "application/jsonl", resp.Header.Get("Content-Type")) + } + + actualContent, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + require.Equal(t, strings.TrimSpace(tc.expectedContent), strings.TrimSpace(string(actualContent))) + require.NoError(t, resp.Body.Close()) + }) + } +} + +// Tests to verify the behavior of the metas endpoint, as described in +// https://docs.google.com/document/d/1s6_9IFEKGQLNh3ueH7SF4Yrx4PW9NSiNFqFIJx0pU-8/ +func TestMetasEndpoint(t *testing.T) { + store := &LocalDirV1{ + RootDir: t.TempDir(), + RootURL: &url.URL{Path: urlPrefix}, + EnableMetasHandler: true, + } + if store.Store(context.Background(), "test-catalog", createTestFS(t)) != nil { + t.Fatal("failed to store test catalog") + } + testServer := httptest.NewServer(store.StorageServerHandler()) + + testCases := []struct { + name string + initRequest func(req *http.Request) error + queryParams string + expectedStatusCode int + expectedContent string + }{ + { + name: "valid query with package schema", + queryParams: "?schema=olm.package", + expectedStatusCode: http.StatusOK, + expectedContent: `{"defaultChannel":"preview_test","name":"webhook_operator_test","schema":"olm.package"}`, + }, + { + name: "valid query with schema and name combination", + queryParams: "?schema=olm.package&name=webhook_operator_test", + expectedStatusCode: http.StatusOK, + expectedContent: `{"defaultChannel":"preview_test","name":"webhook_operator_test","schema":"olm.package"}`, + }, + { + name: "valid query with channel schema and package name combination", + queryParams: "?schema=olm.channel&package=webhook_operator_test", + expectedStatusCode: http.StatusOK, + expectedContent: `{"entries":[{"name":"bundle.v0.0.1"}],"name":"preview_test","package":"webhook_operator_test","schema":"olm.channel"}`, + }, + { + name: "query with all meta fields", + queryParams: "?schema=olm.bundle&package=webhook_operator_test&name=bundle.v0.0.1", + expectedStatusCode: http.StatusOK, + expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}`, + }, + { + name: "valid query for package schema for a package that does not exist", + queryParams: "?schema=olm.package&name=not-present", + expectedStatusCode: http.StatusOK, + expectedContent: "", + }, + { + name: "valid query with package and name", + queryParams: "?package=webhook_operator_test&name=bundle.v0.0.1", + expectedStatusCode: http.StatusOK, + expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}`, + }, + { + name: "query with non-existent schema", + queryParams: "?schema=non_existent_schema", + expectedStatusCode: http.StatusOK, + expectedContent: "", + }, + { + name: "valid query with packageName that returns multiple blobs in json-lines format", + queryParams: "?package=webhook_operator_test", + expectedStatusCode: http.StatusOK, + expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"} +{"entries":[{"name":"bundle.v0.0.1"}],"name":"preview_test","package":"webhook_operator_test","schema":"olm.channel"}`, + }, + { + name: "cached response with If-Modified-Since", + queryParams: "?schema=olm.package", + initRequest: func(req *http.Request) error { + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + resp.Body.Close() + req.Header.Set("If-Modified-Since", resp.Header.Get("Last-Modified")) + return nil + }, + expectedStatusCode: http.StatusNotModified, + expectedContent: "", + }, + { + name: "request with unknown parameters", + queryParams: "?non-existent=foo", + expectedStatusCode: http.StatusBadRequest, + expectedContent: "400 Bad Request", + }, + { + name: "request with duplicate parameters", + queryParams: "?schema=olm.bundle&&schema=olm.bundle", + expectedStatusCode: http.StatusOK, + expectedContent: `{"image":"quaydock.io/namespace/bundle:0.0.3","name":"bundle.v0.0.1","package":"webhook_operator_test","properties":[{"type":"olm.bundle.object","value":{"data":"dW5pbXBvcnRhbnQK"}},{"type":"some.other","value":{"data":"arbitrary-info"}}],"relatedImages":[{"image":"testimage:latest","name":"test"}],"schema":"olm.bundle"}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + reqGet, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil) + require.NoError(t, err) + + if tc.initRequest != nil { + require.NoError(t, tc.initRequest(reqGet)) + } + resp, err := http.DefaultClient.Do(reqGet) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, tc.expectedStatusCode, resp.StatusCode) + if resp.StatusCode == http.StatusOK { + assert.Equal(t, "application/jsonl", resp.Header.Get("Content-Type")) + } + + actualContent, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, tc.expectedContent, strings.TrimSpace(string(actualContent))) + + // Also do a HEAD request + reqHead, err := http.NewRequest(http.MethodHead, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil) + require.NoError(t, err) + if tc.initRequest != nil { + require.NoError(t, tc.initRequest(reqHead)) + } + resp, err = http.DefaultClient.Do(reqHead) + require.NoError(t, err) + require.Equal(t, tc.expectedStatusCode, resp.StatusCode) + actualContent, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Empty(t, string(actualContent)) // HEAD should not return a body + resp.Body.Close() + + // And make sure any other method is not allowed + for _, method := range []string{http.MethodPost, http.MethodPut, http.MethodDelete} { + reqPost, err := http.NewRequest(method, fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas%s", testServer.URL, tc.queryParams), nil) + require.NoError(t, err) + resp, err = http.DefaultClient.Do(reqPost) + require.NoError(t, err) + require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) + resp.Body.Close() + } + }) + } +} + +func TestServerLoadHandling(t *testing.T) { + store := &LocalDirV1{ + RootDir: t.TempDir(), + RootURL: &url.URL{Path: urlPrefix}, + EnableMetasHandler: true, + } + + // Create large test data + largeFS := fstest.MapFS{} + for i := 0; i < 1000; i++ { + largeFS[fmt.Sprintf("meta_%d.json", i)] = &fstest.MapFile{ + Data: []byte(fmt.Sprintf(`{"schema":"olm.bundle","package":"test-op-%d","name":"test-op.v%d.0"}`, i, i)), + } + } + + if err := store.Store(context.Background(), "test-catalog", largeFS); err != nil { + t.Fatal("failed to store test catalog") + } + + testServer := httptest.NewServer(store.StorageServerHandler()) + defer testServer.Close() + + tests := []struct { + name string + concurrent int + requests func(baseURL string) []*http.Request + validateFunc func(t *testing.T, responses []*http.Response, errs []error) + }{ + { + name: "concurrent identical queries", + concurrent: 100, + requests: func(baseURL string) []*http.Request { + var reqs []*http.Request + for i := 0; i < 100; i++ { + req, _ := http.NewRequest(http.MethodGet, + fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas?schema=olm.bundle", baseURL), + nil) + req.Header.Set("Accept", "application/jsonl") + reqs = append(reqs, req) + } + return reqs + }, + validateFunc: func(t *testing.T, responses []*http.Response, errs []error) { + for _, err := range errs { + require.NoError(t, err) + } + for _, resp := range responses { + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, "application/jsonl", resp.Header.Get("Content-Type")) + resp.Body.Close() + } + }, + }, + { + name: "concurrent different queries", + concurrent: 50, + requests: func(baseURL string) []*http.Request { + var reqs []*http.Request + for i := 0; i < 50; i++ { + req, _ := http.NewRequest(http.MethodGet, + fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas?package=test-op-%d", baseURL, i), + nil) + req.Header.Set("Accept", "application/jsonl") + reqs = append(reqs, req) + } + return reqs + }, + validateFunc: func(t *testing.T, responses []*http.Response, errs []error) { + for _, err := range errs { + require.NoError(t, err) + } + for _, resp := range responses { + require.Equal(t, http.StatusOK, resp.StatusCode) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Contains(t, string(body), "test-op-") + resp.Body.Close() + } + }, + }, + { + name: "mixed all and metas endpoints", + concurrent: 40, + requests: func(baseURL string) []*http.Request { + var reqs []*http.Request + for i := 0; i < 20; i++ { + allReq, _ := http.NewRequest(http.MethodGet, + fmt.Sprintf("%s/catalogs/test-catalog/api/v1/all", baseURL), + nil) + metasReq, _ := http.NewRequest(http.MethodGet, + fmt.Sprintf("%s/catalogs/test-catalog/api/v1/metas?schema=olm.bundle", baseURL), + nil) + allReq.Header.Set("Accept", "application/jsonl") + metasReq.Header.Set("Accept", "application/jsonl") + reqs = append(reqs, allReq, metasReq) + } + return reqs + }, + validateFunc: func(t *testing.T, responses []*http.Response, errs []error) { + for _, err := range errs { + require.NoError(t, err) + } + for _, resp := range responses { + require.Equal(t, http.StatusOK, resp.StatusCode) + resp.Body.Close() + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + wg sync.WaitGroup + responses = make([]*http.Response, tt.concurrent) + errs = make([]error, tt.concurrent) + ) + + requests := tt.requests(testServer.URL) + for i := 0; i < tt.concurrent; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + // nolint:bodyclose + // the response body is closed in the validateFunc + resp, err := http.DefaultClient.Do(requests[idx]) + responses[idx] = resp + errs[idx] = err + }(i) + } + + wg.Wait() + tt.validateFunc(t, responses, errs) + }) + } +} + +func createTestFS(t *testing.T) fs.FS { + t.Helper() + testBundleTemplate := `--- +image: %s +name: %s +schema: olm.bundle +package: %s +relatedImages: + - name: %s + image: %s +properties: + - type: olm.bundle.object + value: + data: %s + - type: some.other + value: + data: arbitrary-info +` + + testPackageTemplate := `--- +defaultChannel: %s +name: %s +schema: olm.package +` + + testChannelTemplate := `--- +schema: olm.channel +package: %s +name: %s +entries: + - name: %s +` + testBundleName := "bundle.v0.0.1" + testBundleImage := "quaydock.io/namespace/bundle:0.0.3" + testBundleRelatedImageName := "test" + testBundleRelatedImageImage := "testimage:latest" + testBundleObjectData := "dW5pbXBvcnRhbnQK" + testPackageDefaultChannel := "preview_test" + testPackageName := "webhook_operator_test" + testChannelName := "preview_test" + + testPackage := fmt.Sprintf(testPackageTemplate, testPackageDefaultChannel, testPackageName) + testBundle := fmt.Sprintf(testBundleTemplate, testBundleImage, testBundleName, testPackageName, testBundleRelatedImageName, testBundleRelatedImageImage, testBundleObjectData) + testChannel := fmt.Sprintf(testChannelTemplate, testPackageName, testChannelName, testBundleName) + return &fstest.MapFS{ + "test-catalog.yaml": {Data: []byte( + generateJSONLinesOrFail(t, []byte(testBundle)) + + generateJSONLinesOrFail(t, []byte(testPackage)) + + generateJSONLinesOrFail(t, []byte(testChannel))), + Mode: os.ModePerm}, + } +} + +// generateJSONLinesOrFail takes a byte slice of concatenated JSON objects and returns a JSONlines-formatted string +// or raises a test failure in case of encountering any internal errors +func generateJSONLinesOrFail(t *testing.T, in []byte) string { + var out strings.Builder + reader := bytes.NewReader(in) + + err := declcfg.WalkMetasReader(reader, func(meta *declcfg.Meta, err error) error { + if err != nil { + return err + } + + if meta != nil && meta.Blob != nil { + if meta.Blob[len(meta.Blob)-1] != '\n' { + return fmt.Errorf("blob does not end with newline") + } + } + + _, err = out.Write(meta.Blob) + if err != nil { + return err + } + return nil + }) + require.NoError(t, err) + + return out.String() +} diff --git a/internal/catalogd/storage/storage.go b/internal/catalogd/storage/storage.go new file mode 100644 index 0000000000..af78a669fc --- /dev/null +++ b/internal/catalogd/storage/storage.go @@ -0,0 +1,20 @@ +package storage + +import ( + "context" + "io/fs" + "net/http" +) + +// Instance is a storage instance that stores FBC content of catalogs +// added to a cluster. It can be used to Store or Delete FBC in the +// host's filesystem. It also a manager runnable object, that starts +// a server to serve the content stored. +type Instance interface { + Store(ctx context.Context, catalog string, fsys fs.FS) error + Delete(catalog string) error + ContentExists(catalog string) bool + + BaseURL(catalog string) string + StorageServerHandler() http.Handler +} diff --git a/internal/catalogd/webhook/cluster_catalog_webhook.go b/internal/catalogd/webhook/cluster_catalog_webhook.go new file mode 100644 index 0000000000..3aea45d5d7 --- /dev/null +++ b/internal/catalogd/webhook/cluster_catalog_webhook.go @@ -0,0 +1,42 @@ +package webhook + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// ClusterCatalog wraps the external v1.ClusterCatalog type and implements admission.Defaulter +type ClusterCatalog struct{} + +// Default is the method that will be called by the webhook to apply defaults. +func (r *ClusterCatalog) Default(ctx context.Context, obj runtime.Object) error { + log := log.FromContext(ctx) + log.Info("Invoking Default method for ClusterCatalog", "object", obj) + catalog, ok := obj.(*ocv1.ClusterCatalog) + if !ok { + return fmt.Errorf("expected a ClusterCatalog but got a %T", obj) + } + + // Defaulting logic: add the "olm.operatorframework.io/metadata.name" label + if catalog.Labels == nil { + catalog.Labels = map[string]string{} + } + catalog.Labels[ocv1.MetadataNameLabel] = catalog.GetName() + log.Info("default", ocv1.MetadataNameLabel, catalog.Name, "labels", catalog.Labels) + + return nil +} + +// SetupWebhookWithManager sets up the webhook with the manager +func (r *ClusterCatalog) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(&ocv1.ClusterCatalog{}). + WithDefaulter(r). + Complete() +} diff --git a/internal/catalogd/webhook/cluster_catalog_webhook_test.go b/internal/catalogd/webhook/cluster_catalog_webhook_test.go new file mode 100644 index 0000000000..9d029fd82e --- /dev/null +++ b/internal/catalogd/webhook/cluster_catalog_webhook_test.go @@ -0,0 +1,106 @@ +package webhook + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// Define a dummy struct that implements runtime.Object but isn't a ClusterCatalog +type NotClusterCatalog struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +func (n *NotClusterCatalog) DeepCopyObject() runtime.Object { + return &NotClusterCatalog{} +} + +func TestClusterCatalogDefaulting(t *testing.T) { + tests := map[string]struct { + clusterCatalog runtime.Object + expectedLabels map[string]string + expectError bool + errorMessage string + }{ + "no labels provided, name label added": { + clusterCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + }, + }, + expectedLabels: map[string]string{ + "olm.operatorframework.io/metadata.name": "test-catalog", + }, + expectError: false, + }, + "labels already present, name label added": { + clusterCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Labels: map[string]string{ + "existing": "label", + }, + }, + }, + expectedLabels: map[string]string{ + "olm.operatorframework.io/metadata.name": "test-catalog", + "existing": "label", + }, + expectError: false, + }, + "name label already present, no changes": { + clusterCatalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + Labels: map[string]string{ + "olm.operatorframework.io/metadata.name": "existing-name", + }, + }, + }, + expectedLabels: map[string]string{ + "olm.operatorframework.io/metadata.name": "test-catalog", // Defaulting should still override this to match the object name + }, + expectError: false, + }, + "invalid object type, expect error": { + clusterCatalog: &NotClusterCatalog{ + TypeMeta: metav1.TypeMeta{ + Kind: "NotClusterCatalog", + APIVersion: "v1", + }, + }, + expectedLabels: nil, + expectError: true, + errorMessage: "expected a ClusterCatalog but got a *webhook.NotClusterCatalog", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Arrange + clusterCatalogWrapper := &ClusterCatalog{} + + // Act + err := clusterCatalogWrapper.Default(context.TODO(), tc.clusterCatalog) + + // Assert + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errorMessage) + } else { + assert.NoError(t, err) + if tc.expectedLabels != nil { + labels := tc.clusterCatalog.(*ocv1.ClusterCatalog).Labels + assert.Equal(t, tc.expectedLabels, labels) + } + } + }) + } +} diff --git a/internal/catalogmetadata/cache/cache.go b/internal/catalogmetadata/cache/cache.go deleted file mode 100644 index f5c8a52ebb..0000000000 --- a/internal/catalogmetadata/cache/cache.go +++ /dev/null @@ -1,168 +0,0 @@ -package cache - -import ( - "context" - "fmt" - "io/fs" - "net/http" - "os" - "path/filepath" - "sync" - - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/operator-registry/alpha/declcfg" - - "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" -) - -var _ client.Fetcher = &filesystemCache{} - -// NewFilesystemCache returns a client.Fetcher implementation that uses a -// local filesystem to cache Catalog contents. When fetching the Catalog contents -// it will: -// - Check if the Catalog is cached -// - IF !cached it will fetch from the catalogd HTTP server and cache the response -// - IF cached it will verify the cache is up to date. If it is up to date it will return -// the cached contents, if not it will fetch the new contents from the catalogd HTTP -// server and update the cached contents. -func NewFilesystemCache(cachePath string, clientFunc func() (*http.Client, error)) client.Fetcher { - return &filesystemCache{ - cachePath: cachePath, - mutex: sync.RWMutex{}, - getClient: clientFunc, - cacheDataByCatalogName: map[string]cacheData{}, - } -} - -// cacheData holds information about a catalog -// other than it's contents that is used for -// making decisions on when to attempt to refresh -// the cache. -type cacheData struct { - ResolvedRef string -} - -// FilesystemCache is a cache that -// uses the local filesystem for caching -// catalog contents. It will fetch catalog -// contents if the catalog does not already -// exist in the cache. -type filesystemCache struct { - mutex sync.RWMutex - cachePath string - getClient func() (*http.Client, error) - cacheDataByCatalogName map[string]cacheData -} - -// FetchCatalogContents implements the client.Fetcher interface and -// will fetch the contents for the provided Catalog from the filesystem. -// If the provided Catalog has not yet been cached, it will make a GET -// request to the Catalogd HTTP server to get the Catalog contents and cache -// them. The cache will be updated automatically if a Catalog is noticed to -// have a different resolved image reference. -// The Catalog provided to this function is expected to: -// - Be non-nil -// - Have a non-nil Catalog.Status.ResolvedSource.Image -// This ensures that we are only attempting to fetch catalog contents for Catalog -// resources that have been successfully reconciled, unpacked, and are being served. -// These requirements help ensure that we can rely on status conditions to determine -// when to issue a request to update the cached Catalog contents. -func (fsc *filesystemCache) FetchCatalogContents(ctx context.Context, catalog *catalogd.ClusterCatalog) (fs.FS, error) { - if catalog == nil { - return nil, fmt.Errorf("error: provided catalog must be non-nil") - } - - if catalog.Status.ResolvedSource == nil { - return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource value", catalog.Name) - } - - if catalog.Status.ResolvedSource.Image == nil { - return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource.image value", catalog.Name) - } - - cacheDir := filepath.Join(fsc.cachePath, catalog.Name) - fsc.mutex.RLock() - if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok { - if catalog.Status.ResolvedSource.Image.ResolvedRef == data.ResolvedRef { - fsc.mutex.RUnlock() - return os.DirFS(cacheDir), nil - } - } - fsc.mutex.RUnlock() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalog.Status.ContentURL, nil) - if err != nil { - return nil, fmt.Errorf("error forming request: %v", err) - } - - client, err := fsc.getClient() - if err != nil { - return nil, fmt.Errorf("error getting HTTP client: %w", err) - } - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("error performing request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("error: received unexpected response status code %d", resp.StatusCode) - } - - fsc.mutex.Lock() - defer fsc.mutex.Unlock() - - // make sure we only write if this info hasn't been updated - // by another thread. The check here, if multiple threads are - // updating this, has no way to tell if the current ref is the - // newest possible ref. If another thread has already updated - // this to be the same value, skip the write logic and return - // the cached contents - if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok { - if data.ResolvedRef == catalog.Status.ResolvedSource.Image.ResolvedRef { - return os.DirFS(cacheDir), nil - } - } - - tmpDir, err := os.MkdirTemp(fsc.cachePath, fmt.Sprintf(".%s-", catalog.Name)) - if err != nil { - return nil, fmt.Errorf("error creating temporary directory to unpack catalog metadata: %v", err) - } - - if err := declcfg.WalkMetasReader(resp.Body, func(meta *declcfg.Meta, err error) error { - if err != nil { - return fmt.Errorf("error parsing catalog contents: %v", err) - } - pkgName := meta.Package - if meta.Schema == declcfg.SchemaPackage { - pkgName = meta.Name - } - metaName := meta.Name - if meta.Name == "" { - metaName = meta.Schema - } - metaPath := filepath.Join(tmpDir, pkgName, meta.Schema, metaName+".json") - if err := os.MkdirAll(filepath.Dir(metaPath), os.ModePerm); err != nil { - return fmt.Errorf("error creating directory for catalog metadata: %v", err) - } - if err := os.WriteFile(metaPath, meta.Blob, 0600); err != nil { - return fmt.Errorf("error writing catalog metadata to file: %v", err) - } - return nil - }); err != nil { - return nil, err - } - - if err := os.RemoveAll(cacheDir); err != nil { - return nil, fmt.Errorf("error removing old cache directory: %v", err) - } - if err := os.Rename(tmpDir, cacheDir); err != nil { - return nil, fmt.Errorf("error moving temporary directory to cache directory: %v", err) - } - - fsc.cacheDataByCatalogName[catalog.Name] = cacheData{ - ResolvedRef: catalog.Status.ResolvedSource.Image.ResolvedRef, - } - - return os.DirFS(cacheDir), nil -} diff --git a/internal/catalogmetadata/cache/cache_test.go b/internal/catalogmetadata/cache/cache_test.go deleted file mode 100644 index 05ea28ec59..0000000000 --- a/internal/catalogmetadata/cache/cache_test.go +++ /dev/null @@ -1,344 +0,0 @@ -package cache_test - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "maps" - "net/http" - "path/filepath" - "testing" - "testing/fstest" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/operator-registry/alpha/declcfg" - - "github.com/operator-framework/operator-controller/internal/catalogmetadata/cache" -) - -const ( - package1 = `{ - "schema": "olm.package", - "name": "fake1" - }` - - bundle1 = `{ - "schema": "olm.bundle", - "name": "fake1.v1.0.0", - "package": "fake1", - "image": "fake-image", - "properties": [ - { - "type": "olm.package", - "value": {"packageName":"fake1","version":"1.0.0"} - } - ] - }` - - stableChannel = `{ - "schema": "olm.channel", - "name": "stable", - "package": "fake1", - "entries": [ - { - "name": "fake1.v1.0.0" - } - ] - }` -) - -var defaultFS = fstest.MapFS{ - "fake1/olm.package/fake1.json": &fstest.MapFile{Data: []byte(package1)}, - "fake1/olm.bundle/fake1.v1.0.0.json": &fstest.MapFile{Data: []byte(bundle1)}, - "fake1/olm.channel/stable.json": &fstest.MapFile{Data: []byte(stableChannel)}, -} - -func TestFilesystemCache(t *testing.T) { - type test struct { - name string - catalog *catalogd.ClusterCatalog - contents fstest.MapFS - wantErr bool - tripper *mockTripper - testCaching bool - shouldHitCache bool - } - for _, tt := range []test{ - { - name: "valid non-cached fetch", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ResolvedImageSource{ - ResolvedRef: "fake/catalog@sha256:fakesha", - }, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{}, - }, - { - name: "valid cached fetch", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ResolvedImageSource{ - ResolvedRef: "fake/catalog@sha256:fakesha", - }, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{}, - testCaching: true, - shouldHitCache: true, - }, - { - name: "cached update fetch with changes", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ResolvedImageSource{ - ResolvedRef: "fake/catalog@sha256:fakesha", - }, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{}, - testCaching: true, - shouldHitCache: false, - }, - { - name: "fetch error", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ResolvedImageSource{ - ResolvedRef: "fake/catalog@sha256:fakesha", - }, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{shouldError: true}, - wantErr: true, - }, - { - name: "fetch internal server error response", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ResolvedImageSource{ - ResolvedRef: "fake/catalog@sha256:fakesha", - }, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{serverError: true}, - wantErr: true, - }, - { - name: "nil catalog", - catalog: nil, - contents: defaultFS, - tripper: &mockTripper{serverError: true}, - wantErr: true, - }, - { - name: "nil catalog.status.resolvedSource", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: nil, - }, - }, - contents: defaultFS, - tripper: &mockTripper{serverError: true}, - wantErr: true, - }, - { - name: "nil catalog.status.resolvedSource.image", - catalog: &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-catalog", - }, - Status: catalogd.ClusterCatalogStatus{ - ResolvedSource: &catalogd.ResolvedCatalogSource{ - Image: nil, - }, - }, - }, - contents: defaultFS, - tripper: &mockTripper{serverError: true}, - wantErr: true, - }, - } { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - cacheDir := t.TempDir() - tt.tripper.content = make(fstest.MapFS) - maps.Copy(tt.tripper.content, tt.contents) - httpClient := http.DefaultClient - httpClient.Transport = tt.tripper - c := cache.NewFilesystemCache(cacheDir, func() (*http.Client, error) { - return httpClient, nil - }) - - actualFS, err := c.FetchCatalogContents(ctx, tt.catalog) - if !tt.wantErr { - assert.NoError(t, err) - assert.NoError(t, equalFilesystems(tt.contents, actualFS)) - } else { - assert.Error(t, err) - } - - if tt.testCaching { - if !tt.shouldHitCache { - tt.catalog.Status.ResolvedSource.Image.ResolvedRef = "fake/catalog@sha256:shafake" - } - tt.tripper.content["foobar/olm.package/foobar.json"] = &fstest.MapFile{Data: []byte(`{"schema": "olm.package", "name": "foobar"}`)} - actualFS, err := c.FetchCatalogContents(ctx, tt.catalog) - assert.NoError(t, err) - if !tt.shouldHitCache { - assert.NoError(t, equalFilesystems(tt.tripper.content, actualFS)) - assert.ErrorContains(t, equalFilesystems(tt.contents, actualFS), "foobar/olm.package/foobar.json") - } else { - assert.NoError(t, equalFilesystems(tt.contents, actualFS)) - } - } - }) - } -} - -var _ http.RoundTripper = &mockTripper{} - -type mockTripper struct { - content fstest.MapFS - shouldError bool - serverError bool -} - -func (mt *mockTripper) RoundTrip(_ *http.Request) (*http.Response, error) { - if mt.shouldError { - return nil, errors.New("mock tripper error") - } - - if mt.serverError { - return &http.Response{ - StatusCode: http.StatusInternalServerError, - Body: http.NoBody, - }, nil - } - - pr, pw := io.Pipe() - - go func() { - _ = pw.CloseWithError(declcfg.WalkMetasFS(context.Background(), mt.content, func(_ string, meta *declcfg.Meta, err error) error { - if err != nil { - return err - } - _, err = pw.Write(meta.Blob) - return err - })) - }() - - return &http.Response{ - StatusCode: http.StatusOK, - Body: pr, - }, nil -} - -func equalFilesystems(expected, actual fs.FS) error { - normalizeJSON := func(data []byte) []byte { - var v interface{} - if err := json.Unmarshal(data, &v); err != nil { - return data - } - norm, err := json.Marshal(v) - if err != nil { - return data - } - return norm - } - compare := func(expected, actual fs.FS, path string) error { - expectedData, expectedErr := fs.ReadFile(expected, path) - actualData, actualErr := fs.ReadFile(actual, path) - - switch { - case expectedErr == nil && actualErr != nil: - return fmt.Errorf("path %q: read error in actual FS: %v", path, actualErr) - case expectedErr != nil && actualErr == nil: - return fmt.Errorf("path %q: read error in expected FS: %v", path, expectedErr) - case expectedErr != nil && actualErr != nil && expectedErr.Error() != actualErr.Error(): - return fmt.Errorf("path %q: different read errors: expected: %v, actual: %v", path, expectedErr, actualErr) - } - - if filepath.Ext(path) == ".json" { - expectedData = normalizeJSON(expectedData) - actualData = normalizeJSON(actualData) - } - - if !bytes.Equal(expectedData, actualData) { - return fmt.Errorf("path %q: file contents do not match: %s", path, cmp.Diff(string(expectedData), string(actualData))) - } - return nil - } - - paths := sets.New[string]() - for _, fsys := range []fs.FS{expected, actual} { - if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - paths.Insert(path) - return nil - }); err != nil { - return err - } - } - - var cmpErrs []error - for _, path := range sets.List(paths) { - if err := compare(expected, actual, path); err != nil { - cmpErrs = append(cmpErrs, err) - } - } - return errors.Join(cmpErrs...) -} diff --git a/internal/catalogmetadata/client/client.go b/internal/catalogmetadata/client/client.go deleted file mode 100644 index 2b0663d486..0000000000 --- a/internal/catalogmetadata/client/client.go +++ /dev/null @@ -1,67 +0,0 @@ -package client - -import ( - "context" - "errors" - "fmt" - "io/fs" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/operator-registry/alpha/declcfg" -) - -// Fetcher is an interface to facilitate fetching -// catalog contents from catalogd. -type Fetcher interface { - // FetchCatalogContents fetches contents from the catalogd HTTP - // server for the catalog provided. It returns a fs.FS containing the FBC contents. - // Each sub directory contains FBC for a single package - // and the directory name is package name. - // Returns an error if any occur. - FetchCatalogContents(ctx context.Context, catalog *catalogd.ClusterCatalog) (fs.FS, error) -} - -func New(fetcher Fetcher) *Client { - return &Client{ - fetcher: fetcher, - } -} - -// Client is reading catalog metadata -type Client struct { - // fetcher is the Fetcher to use for fetching catalog contents - fetcher Fetcher -} - -func (c *Client) GetPackage(ctx context.Context, catalog *catalogd.ClusterCatalog, pkgName string) (*declcfg.DeclarativeConfig, error) { - // if the catalog has not been successfully unpacked, report an error. This ensures that our - // reconciles are deterministic and wait for all desired catalogs to be ready. - if !meta.IsStatusConditionPresentAndEqual(catalog.Status.Conditions, catalogd.TypeUnpacked, metav1.ConditionTrue) { - return nil, fmt.Errorf("catalog %q is not unpacked", catalog.Name) - } - - catalogFsys, err := c.fetcher.FetchCatalogContents(ctx, catalog) - if err != nil { - return nil, fmt.Errorf("error fetching catalog contents: %v", err) - } - - pkgFsys, err := fs.Sub(catalogFsys, pkgName) - if err != nil { - if !errors.Is(err, fs.ErrNotExist) { - return nil, fmt.Errorf("error getting package %q: %v", pkgName, err) - } - return &declcfg.DeclarativeConfig{}, nil - } - - pkgFBC, err := declcfg.LoadFS(ctx, pkgFsys) - if err != nil { - if !errors.Is(err, fs.ErrNotExist) { - return nil, fmt.Errorf("error loading package %q: %v", pkgName, err) - } - return &declcfg.DeclarativeConfig{}, nil - } - return pkgFBC, nil -} diff --git a/internal/catalogmetadata/client/client_test.go b/internal/catalogmetadata/client/client_test.go deleted file mode 100644 index beacc4512f..0000000000 --- a/internal/catalogmetadata/client/client_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package client_test - -import ( - "context" - "errors" - "io/fs" - "testing" - "testing/fstest" - - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/operator-registry/alpha/declcfg" - - catalogClient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" -) - -func TestClientNew(t *testing.T) { - testFS := fstest.MapFS{ - "pkg-present/olm.package/pkg-present.json": &fstest.MapFile{Data: []byte(`{"schema": "olm.package","name": "pkg-present"}`)}, - } - - type testCase struct { - name string - catalog *catalogd.ClusterCatalog - pkgName string - fetcher catalogClient.Fetcher - assert func(*testing.T, *declcfg.DeclarativeConfig, error) - } - for _, tc := range []testCase{ - { - name: "not unpacked", - catalog: &catalogd.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: "catalog-1"}}, - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { return testFS, nil }), - assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { - assert.ErrorContains(t, err, `catalog "catalog-1" is not unpacked`) - }, - }, - { - name: "unpacked, fetcher returns error", - catalog: &catalogd.ClusterCatalog{ - Status: catalogd.ClusterCatalogStatus{Conditions: []metav1.Condition{{Type: catalogd.TypeUnpacked, Status: metav1.ConditionTrue}}}, - }, - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { return nil, errors.New("fetch error") }), - assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { - assert.ErrorContains(t, err, `error fetching catalog contents: fetch error`) - }, - }, - { - name: "unpacked, invalid package path", - catalog: &catalogd.ClusterCatalog{ - Status: catalogd.ClusterCatalogStatus{Conditions: []metav1.Condition{{Type: catalogd.TypeUnpacked, Status: metav1.ConditionTrue}}}, - }, - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { return testFS, nil }), - pkgName: "/", - assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { - assert.ErrorContains(t, err, `error getting package "/"`) - }, - }, - { - name: "unpacked, package missing", - catalog: &catalogd.ClusterCatalog{ - Status: catalogd.ClusterCatalogStatus{Conditions: []metav1.Condition{{Type: catalogd.TypeUnpacked, Status: metav1.ConditionTrue}}}, - }, - pkgName: "pkg-missing", - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { return testFS, nil }), - assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { - assert.NoError(t, err) - assert.Equal(t, &declcfg.DeclarativeConfig{}, fbc) - }, - }, - { - name: "unpacked, invalid package present", - catalog: &catalogd.ClusterCatalog{ - Status: catalogd.ClusterCatalogStatus{Conditions: []metav1.Condition{{Type: catalogd.TypeUnpacked, Status: metav1.ConditionTrue}}}, - }, - pkgName: "invalid-pkg-present", - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { - return fstest.MapFS{ - "invalid-pkg-present/olm.package/invalid-pkg-present.json": &fstest.MapFile{Data: []byte(`{"schema": "olm.package","name": 12345}`)}, - }, nil - }), - assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { - assert.ErrorContains(t, err, `error loading package "invalid-pkg-present"`) - assert.Nil(t, fbc) - }, - }, - { - name: "unpacked, package present", - catalog: &catalogd.ClusterCatalog{ - Status: catalogd.ClusterCatalogStatus{Conditions: []metav1.Condition{{Type: catalogd.TypeUnpacked, Status: metav1.ConditionTrue}}}, - }, - pkgName: "pkg-present", - fetcher: fetcherFunc(func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) { return testFS, nil }), - assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { - assert.NoError(t, err) - assert.Equal(t, &declcfg.DeclarativeConfig{Packages: []declcfg.Package{{Schema: declcfg.SchemaPackage, Name: "pkg-present"}}}, fbc) - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - c := catalogClient.New(tc.fetcher) - fbc, err := c.GetPackage(context.Background(), tc.catalog, tc.pkgName) - tc.assert(t, fbc, err) - }) - } -} - -type fetcherFunc func(context.Context, *catalogd.ClusterCatalog) (fs.FS, error) - -func (f fetcherFunc) FetchCatalogContents(ctx context.Context, catalog *catalogd.ClusterCatalog) (fs.FS, error) { - return f(ctx, catalog) -} diff --git a/internal/catalogmetadata/filter/filter_test.go b/internal/catalogmetadata/filter/filter_test.go deleted file mode 100644 index 77d12c99f2..0000000000 --- a/internal/catalogmetadata/filter/filter_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package filter_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - - "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" -) - -func TestFilter(t *testing.T) { - for _, tt := range []struct { - name string - predicate filter.Predicate[declcfg.Bundle] - want []declcfg.Bundle - }{ - { - name: "simple filter with one predicate", - predicate: func(bundle declcfg.Bundle) bool { - return bundle.Name == "extension1.v1" - }, - want: []declcfg.Bundle{ - {Name: "extension1.v1", Package: "extension1", Image: "fake1"}, - }, - }, - { - name: "filter with Not predicate", - predicate: filter.Not(func(bundle declcfg.Bundle) bool { - return bundle.Name == "extension1.v1" - }), - want: []declcfg.Bundle{ - {Name: "extension1.v2", Package: "extension1", Image: "fake2"}, - {Name: "extension2.v1", Package: "extension2", Image: "fake1"}, - }, - }, - { - name: "filter with And predicate", - predicate: filter.And( - func(bundle declcfg.Bundle) bool { - return bundle.Name == "extension1.v1" - }, - func(bundle declcfg.Bundle) bool { - return bundle.Image == "fake1" - }, - ), - want: []declcfg.Bundle{ - {Name: "extension1.v1", Package: "extension1", Image: "fake1"}, - }, - }, - { - name: "filter with Or predicate", - predicate: filter.Or( - func(bundle declcfg.Bundle) bool { - return bundle.Name == "extension1.v1" - }, - func(bundle declcfg.Bundle) bool { - return bundle.Image == "fake1" - }, - ), - want: []declcfg.Bundle{ - {Name: "extension1.v1", Package: "extension1", Image: "fake1"}, - {Name: "extension2.v1", Package: "extension2", Image: "fake1"}, - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - in := []declcfg.Bundle{ - {Name: "extension1.v1", Package: "extension1", Image: "fake1"}, - {Name: "extension1.v2", Package: "extension1", Image: "fake2"}, - {Name: "extension2.v1", Package: "extension2", Image: "fake1"}, - } - - actual := filter.Filter(in, tt.predicate) - assert.Equal(t, tt.want, actual) - }) - } -} diff --git a/internal/catalogmetadata/filter/successors.go b/internal/catalogmetadata/filter/successors.go deleted file mode 100644 index cfae6eff0f..0000000000 --- a/internal/catalogmetadata/filter/successors.go +++ /dev/null @@ -1,106 +0,0 @@ -package filter - -import ( - "fmt" - - mmsemver "github.com/Masterminds/semver/v3" - bsemver "github.com/blang/semver/v4" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/features" -) - -func SuccessorsOf(installedBundle ocv1alpha1.BundleMetadata, channels ...declcfg.Channel) (Predicate[declcfg.Bundle], error) { - var successors successorsPredicateFunc = legacySuccessor - if features.OperatorControllerFeatureGate.Enabled(features.ForceSemverUpgradeConstraints) { - successors = semverSuccessor - } - - installedBundleVersion, err := mmsemver.NewVersion(installedBundle.Version) - if err != nil { - return nil, fmt.Errorf("parsing installed bundle %q version %q: %w", installedBundle.Name, installedBundle.Version, err) - } - - installedVersionConstraint, err := mmsemver.NewConstraint(installedBundleVersion.String()) - if err != nil { - return nil, fmt.Errorf("parsing installed version constraint %q: %w", installedBundleVersion.String(), err) - } - - successorsPredicate, err := successors(installedBundle, channels...) - if err != nil { - return nil, fmt.Errorf("getting successorsPredicate: %w", err) - } - - // We need either successors or current version (no upgrade) - return Or( - successorsPredicate, - InMastermindsSemverRange(installedVersionConstraint), - ), nil -} - -// successorsPredicateFunc returns a predicate to find successors -// for a bundle. Predicate must not include the current version. -type successorsPredicateFunc func(installedBundle ocv1alpha1.BundleMetadata, channels ...declcfg.Channel) (Predicate[declcfg.Bundle], error) - -func legacySuccessor(installedBundle ocv1alpha1.BundleMetadata, channels ...declcfg.Channel) (Predicate[declcfg.Bundle], error) { - installedBundleVersion, err := bsemver.Parse(installedBundle.Version) - if err != nil { - return nil, fmt.Errorf("error parsing installed bundle version: %w", err) - } - - isSuccessor := func(candidateBundleEntry declcfg.ChannelEntry) bool { - if candidateBundleEntry.Replaces == installedBundle.Name { - return true - } - for _, skip := range candidateBundleEntry.Skips { - if skip == installedBundle.Name { - return true - } - } - if candidateBundleEntry.SkipRange != "" { - skipRange, err := bsemver.ParseRange(candidateBundleEntry.SkipRange) - if err == nil && skipRange(installedBundleVersion) { - return true - } - } - return false - } - - return func(candidateBundle declcfg.Bundle) bool { - for _, ch := range channels { - for _, chEntry := range ch.Entries { - if candidateBundle.Name == chEntry.Name && isSuccessor(chEntry) { - return true - } - } - } - return false - }, nil -} - -// semverSuccessor returns a predicate to find successors based on Semver. -// Successors will not include versions outside the major version of the -// installed bundle as major version is intended to indicate breaking changes. -// -// NOTE: semverSuccessor does not consider channels since there is no information -// in a channel entry that is necessary to determine if a bundle is a successor. -// A semver range check is the only necessary element. If filtering by channel -// membership is necessary, an additional filter for that purpose should be applied. -func semverSuccessor(installedBundle ocv1alpha1.BundleMetadata, _ ...declcfg.Channel) (Predicate[declcfg.Bundle], error) { - currentVersion, err := mmsemver.NewVersion(installedBundle.Version) - if err != nil { - return nil, err - } - - // Based on current version create a caret range comparison constraint - // to allow only minor and patch version as successors and exclude current version. - constraintStr := fmt.Sprintf("^%[1]s, != %[1]s", currentVersion.String()) - wantedVersionRangeConstraint, err := mmsemver.NewConstraint(constraintStr) - if err != nil { - return nil, err - } - - return InMastermindsSemverRange(wantedVersionRangeConstraint), nil -} diff --git a/internal/catalogmetadata/filter/successors_test.go b/internal/catalogmetadata/filter/successors_test.go deleted file mode 100644 index 1d7cc72c39..0000000000 --- a/internal/catalogmetadata/filter/successors_test.go +++ /dev/null @@ -1,390 +0,0 @@ -package filter - -import ( - "slices" - "testing" - - bsemver "github.com/blang/semver/v4" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - featuregatetesting "k8s.io/component-base/featuregate/testing" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-registry/alpha/property" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/bundleutil" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/compare" - "github.com/operator-framework/operator-controller/internal/features" -) - -func TestSuccessorsPredicateWithForceSemverUpgradeConstraintsEnabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true) - - const testPackageName = "test-package" - channelSet := map[string]declcfg.Channel{ - testPackageName: { - Package: testPackageName, - Name: "stable", - }, - } - - bundleSet := map[string]declcfg.Bundle{ - // Major version zero is for initial development and - // has different update behaviour than versions >= 1.0.0: - // - In versions 0.0.y updates are not allowed when using semver constraints - // - In versions 0.x.y only patch updates are allowed (>= 0.x.y and < 0.x+1.0) - // This means that we need in test data bundles that cover these three version ranges. - "test-package.v0.0.1": { - Name: "test-package.v0.0.1", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.0.1", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.0.1"), - }, - }, - "test-package.v0.0.2": { - Name: "test-package.v0.0.2", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.0.2", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.0.2"), - }, - }, - "test-package.v0.1.0": { - Name: "test-package.v0.1.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.1.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.1.0"), - }, - }, - "test-package.v0.1.1": { - Name: "test-package.v0.1.1", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.1.1", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.1.1"), - }, - }, - "test-package.v0.1.2": { - Name: "test-package.v0.1.2", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.1.2", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.1.2"), - }, - }, - "test-package.v0.2.0": { - Name: "test-package.v0.2.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v0.2.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "0.2.0"), - }, - }, - "test-package.v2.0.0": { - Name: "test-package.v2.0.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.0.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.0.0"), - }, - }, - "test-package.v2.1.0": { - Name: "test-package.v2.1.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.1.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.1.0"), - }, - }, - "test-package.v2.2.0": { - Name: "test-package.v2.2.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.2.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.2.0"), - }, - }, - // We need a bundle with a different major version to ensure - // that we do not allow upgrades from one major version to another - "test-package.v3.0.0": { - Name: "test-package.v3.0.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v3.0.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "3.0.0"), - }, - }, - } - - for _, b := range bundleSet { - ch := channelSet[b.Package] - ch.Entries = append(ch.Entries, declcfg.ChannelEntry{Name: b.Name}) - channelSet[b.Package] = ch - } - - for _, tt := range []struct { - name string - installedBundle ocv1alpha1.BundleMetadata - expectedResult []declcfg.Bundle - }{ - { - name: "with non-zero major version", - installedBundle: bundleutil.MetadataFor("test-package.v2.0.0", bsemver.MustParse("2.0.0")), - expectedResult: []declcfg.Bundle{ - // Updates are allowed within the major version - bundleSet["test-package.v2.2.0"], - bundleSet["test-package.v2.1.0"], - bundleSet["test-package.v2.0.0"], - }, - }, - { - name: "with zero major and zero minor version", - installedBundle: bundleutil.MetadataFor("test-package.v0.0.1", bsemver.MustParse("0.0.1")), - expectedResult: []declcfg.Bundle{ - // No updates are allowed in major version zero when minor version is also zero - bundleSet["test-package.v0.0.1"], - }, - }, - { - name: "with zero major and non-zero minor version", - installedBundle: bundleutil.MetadataFor("test-package.v0.1.0", bsemver.MustParse("0.1.0")), - expectedResult: []declcfg.Bundle{ - // Patch version updates are allowed within the minor version - bundleSet["test-package.v0.1.2"], - bundleSet["test-package.v0.1.1"], - bundleSet["test-package.v0.1.0"], - }, - }, - { - name: "installed bundle not found", - installedBundle: ocv1alpha1.BundleMetadata{ - Name: "test-package.v9.0.0", - Version: "9.0.0", - }, - expectedResult: []declcfg.Bundle{}, - }, - } { - t.Run(tt.name, func(t *testing.T) { - successors, err := SuccessorsOf(tt.installedBundle, channelSet[testPackageName]) - assert.NoError(t, err) - - allBundles := make([]declcfg.Bundle, 0, len(bundleSet)) - for _, bundle := range bundleSet { - allBundles = append(allBundles, bundle) - } - result := Filter(allBundles, successors) - - // sort before comparison for stable order - slices.SortFunc(result, compare.ByVersion) - - gocmpopts := []cmp.Option{ - cmpopts.IgnoreUnexported(declcfg.Bundle{}), - } - require.Empty(t, cmp.Diff(result, tt.expectedResult, gocmpopts...)) - }) - } -} - -func TestSuccessorsPredicateWithForceSemverUpgradeConstraintsDisabled(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false) - - const testPackageName = "test-package" - channelSet := map[string]declcfg.Channel{ - testPackageName: { - Name: "stable", - Package: testPackageName, - Entries: []declcfg.ChannelEntry{ - { - Name: "test-package.v2.0.0", - }, - { - Name: "test-package.v2.1.0", - Replaces: "test-package.v2.0.0", - }, - { - Name: "test-package.v2.2.0", - Replaces: "test-package.v2.1.0", - }, - { - Name: "test-package.v2.2.1", - }, - { - Name: "test-package.v2.3.0", - Replaces: "test-package.v2.2.0", - Skips: []string{ - "test-package.v2.2.1", - }, - }, - { - Name: "test-package.v2.4.0", - SkipRange: ">=2.3.0 <2.4.0", - }, - }, - }, - } - - bundleSet := map[string]declcfg.Bundle{ - "test-package.v2.0.0": { - Name: "test-package.v2.0.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.0.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.0.0"), - }, - }, - "test-package.v2.1.0": { - Name: "test-package.v2.1.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.1.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.1.0"), - }, - }, - "test-package.v2.2.0": { - Name: "test-package.v2.2.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.2.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.2.0"), - }, - }, - "test-package.v2.2.1": { - Name: "test-package.v2.2.1", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.2.1", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.2.1"), - }, - }, - "test-package.v2.3.0": { - Name: "test-package.v2.3.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.3.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.3.0"), - }, - }, - "test-package.v2.4.0": { - Name: "test-package.v2.4.0", - Package: testPackageName, - Image: "registry.io/repo/test-package@v2.4.0", - Properties: []property.Property{ - property.MustBuildPackage(testPackageName, "2.4.0"), - }, - }, - } - - for _, tt := range []struct { - name string - installedBundle ocv1alpha1.BundleMetadata - expectedResult []declcfg.Bundle - }{ - { - name: "respect replaces directive from catalog", - installedBundle: bundleutil.MetadataFor("test-package.v2.0.0", bsemver.MustParse("2.0.0")), - expectedResult: []declcfg.Bundle{ - // Must only have two bundle: - // - the one which replaces the current version - // - the current version (to allow to stay on the current version) - bundleSet["test-package.v2.1.0"], - bundleSet["test-package.v2.0.0"], - }, - }, - { - name: "respect skips directive from catalog", - installedBundle: bundleutil.MetadataFor("test-package.v2.2.1", bsemver.MustParse("2.2.1")), - expectedResult: []declcfg.Bundle{ - // Must only have two bundle: - // - the one which skips the current version - // - the current version (to allow to stay on the current version) - bundleSet["test-package.v2.3.0"], - bundleSet["test-package.v2.2.1"], - }, - }, - { - name: "respect skipRange directive from catalog", - installedBundle: bundleutil.MetadataFor("test-package.v2.3.0", bsemver.MustParse("2.3.0")), - expectedResult: []declcfg.Bundle{ - // Must only have two bundle: - // - the one which is skipRanges the current version - // - the current version (to allow to stay on the current version) - bundleSet["test-package.v2.4.0"], - bundleSet["test-package.v2.3.0"], - }, - }, - { - name: "installed bundle not found", - installedBundle: ocv1alpha1.BundleMetadata{ - Name: "test-package.v9.0.0", - Version: "9.0.0", - }, - expectedResult: []declcfg.Bundle{}, - }, - } { - t.Run(tt.name, func(t *testing.T) { - successors, err := SuccessorsOf(tt.installedBundle, channelSet[testPackageName]) - assert.NoError(t, err) - - allBundles := make([]declcfg.Bundle, 0, len(bundleSet)) - for _, bundle := range bundleSet { - allBundles = append(allBundles, bundle) - } - result := Filter(allBundles, successors) - - // sort before comparison for stable order - slices.SortFunc(result, compare.ByVersion) - - gocmpopts := []cmp.Option{ - cmpopts.IgnoreUnexported(declcfg.Bundle{}), - } - require.Empty(t, cmp.Diff(result, tt.expectedResult, gocmpopts...)) - }) - } -} - -func TestLegacySuccessor(t *testing.T) { - fakeChannel := declcfg.Channel{ - Entries: []declcfg.ChannelEntry{ - { - Name: "package1.v0.0.2", - Replaces: "package1.v0.0.1", - }, - { - Name: "package1.v0.0.3", - Replaces: "package1.v0.0.2", - }, - { - Name: "package1.v0.0.4", - Skips: []string{"package1.v0.0.1"}, - }, - { - Name: "package1.v0.0.5", - SkipRange: "<=0.0.1", - }, - }, - } - installedBundle := ocv1alpha1.BundleMetadata{ - Name: "package1.v0.0.1", - Version: "0.0.1", - } - - b2 := declcfg.Bundle{Name: "package1.v0.0.2"} - b3 := declcfg.Bundle{Name: "package1.v0.0.3"} - b4 := declcfg.Bundle{Name: "package1.v0.0.4"} - b5 := declcfg.Bundle{Name: "package1.v0.0.5"} - emptyBundle := declcfg.Bundle{} - - f, err := legacySuccessor(installedBundle, fakeChannel) - assert.NoError(t, err) - - assert.True(t, f(b2)) - assert.False(t, f(b3)) - assert.True(t, f(b4)) - assert.True(t, f(b5)) - assert.False(t, f(emptyBundle)) -} diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go deleted file mode 100644 index c2830dc291..0000000000 --- a/internal/controllers/clusterextension_controller.go +++ /dev/null @@ -1,502 +0,0 @@ -/* -Copyright 2023. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "errors" - "fmt" - "io/fs" - "strings" - "time" - - "github.com/go-logr/logr" - "helm.sh/helm/v3/pkg/storage/driver" - "k8s.io/apimachinery/pkg/api/equality" - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/event" - crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" - crhandler "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" - "github.com/operator-framework/operator-registry/alpha/declcfg" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/bundleutil" - "github.com/operator-framework/operator-controller/internal/conditionsets" - "github.com/operator-framework/operator-controller/internal/contentmanager" - "github.com/operator-framework/operator-controller/internal/labels" - "github.com/operator-framework/operator-controller/internal/resolve" - rukpaksource "github.com/operator-framework/operator-controller/internal/rukpak/source" -) - -const ( - ClusterExtensionCleanupUnpackCacheFinalizer = "olm.operatorframework.io/cleanup-unpack-cache" - ClusterExtensionCleanupContentManagerCacheFinalizer = "olm.operatorframework.io/cleanup-contentmanager-cache" -) - -// ClusterExtensionReconciler reconciles a ClusterExtension object -type ClusterExtensionReconciler struct { - client.Client - Resolver resolve.Resolver - Unpacker rukpaksource.Unpacker - Applier Applier - Manager contentmanager.Manager - controller crcontroller.Controller - cache cache.Cache - InstalledBundleGetter InstalledBundleGetter - Finalizers crfinalizer.Finalizers -} - -type Applier interface { - // Apply applies the content in the provided fs.FS using the configuration of the provided ClusterExtension. - // It also takes in a map[string]string to be applied to all applied resources as labels and another - // map[string]string used to create a unique identifier for a stored reference to the resources created. - Apply(context.Context, fs.FS, *ocv1alpha1.ClusterExtension, map[string]string, map[string]string) ([]client.Object, string, error) -} - -type InstalledBundleGetter interface { - GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error) -} - -//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch;update;patch -//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/status,verbs=update;patch -//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/finalizers,verbs=update -//+kubebuilder:rbac:namespace=system,groups=core,resources=secrets,verbs=create;update;patch;delete;deletecollection;get;list;watch -//+kubebuilder:rbac:groups=core,resources=serviceaccounts/token,verbs=create -//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get - -//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=list;watch - -// The operator controller needs to watch all the bundle objects and reconcile accordingly. Though not ideal, but these permissions are required. -// This has been taken from rukpak, and an issue was created before to discuss it: https://github.com/operator-framework/rukpak/issues/800. -func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - l := log.FromContext(ctx).WithName("operator-controller") - ctx = log.IntoContext(ctx, l) - - l.V(1).Info("reconcile starting") - defer l.V(1).Info("reconcile ending") - - existingExt := &ocv1alpha1.ClusterExtension{} - if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - reconciledExt := existingExt.DeepCopy() - res, err := r.reconcile(ctx, reconciledExt) - updateError := err - - // Do checks before any Update()s, as Update() may modify the resource structure! - updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) - updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers) - unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt) - - if updateStatus { - if err := r.Client.Status().Update(ctx, reconciledExt); err != nil { - updateError = errors.Join(updateError, fmt.Errorf("error updating status: %v", err)) - } - } - - if unexpectedFieldsChanged { - panic("spec or metadata changed by reconciler") - } - - if updateFinalizers { - if err := r.Client.Update(ctx, reconciledExt); err != nil { - updateError = errors.Join(updateError, fmt.Errorf("error updating finalizers: %v", err)) - } - } - - return res, updateError -} - -// ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension, -// and assigns a specified reason and custom message to any missing condition. -func ensureAllConditionsWithReason(ext *ocv1alpha1.ClusterExtension, reason v1alpha1.ConditionReason, message string) { - for _, condType := range conditionsets.ConditionTypes { - cond := apimeta.FindStatusCondition(ext.Status.Conditions, condType) - if cond == nil { - // Create a new condition with a valid reason and add it - newCond := metav1.Condition{ - Type: condType, - Status: metav1.ConditionFalse, - Reason: string(reason), - Message: message, - ObservedGeneration: ext.GetGeneration(), - LastTransitionTime: metav1.NewTime(time.Now()), - } - ext.Status.Conditions = append(ext.Status.Conditions, newCond) - } - } -} - -// Compare resources - ignoring status & metadata.finalizers -func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool { - a.Status, b.Status = ocv1alpha1.ClusterExtensionStatus{}, ocv1alpha1.ClusterExtensionStatus{} - a.Finalizers, b.Finalizers = []string{}, []string{} - return !equality.Semantic.DeepEqual(a, b) -} - -// Helper function to do the actual reconcile -// -// Today we always return ctrl.Result{} and an error. -// But in the future we might update this function -// to return different results (e.g. requeue). -// -/* The reconcile functions performs the following major tasks: -1. Resolution: Run the resolution to find the bundle from the catalog which needs to be installed. -2. Validate: Ensure that the bundle returned from the resolution for install meets our requirements. -3. Unpack: Unpack the contents from the bundle and store in a localdir in the pod. -4. Install: The process of installing involves: -4.1 Converting the CSV in the bundle into a set of plain k8s objects. -4.2 Generating a chart from k8s objects. -4.3 Apply the release on cluster. -*/ -//nolint:unparam -func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) { - l := log.FromContext(ctx) - - l.V(1).Info("handling finalizers") - finalizeResult, err := r.Finalizers.Finalize(ctx, ext) - if err != nil { - // TODO: For now, this error handling follows the pattern of other error handling. - // Namely: zero just about everything out, throw our hands up, and return an error. - // This is not ideal, and we should consider a more nuanced approach that resolves - // as much status as possible before returning, or at least keeps previous state if - // it is properly labeled with its observed generation. - setInstallStatus(ext, nil) - setResolutionStatus(ext, nil) - setResolvedStatusConditionFailed(ext, err.Error()) - ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonFailed, err.Error()) - return ctrl.Result{}, err - } - if finalizeResult.Updated || finalizeResult.StatusUpdated { - // On create: make sure the finalizer is applied before we do anything - // On delete: make sure we do nothing after the finalizer is removed - return ctrl.Result{}, nil - } - - l.V(1).Info("getting installed bundle") - installedBundle, err := r.InstalledBundleGetter.GetInstalledBundle(ctx, ext) - if err != nil { - setInstallStatus(ext, nil) - // TODO: use Installed=Unknown - setInstalledStatusConditionFailed(ext, err.Error()) - return ctrl.Result{}, err - } - - // run resolution - l.V(1).Info("resolving bundle") - resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.Resolver.Resolve(ctx, ext, installedBundle) - if err != nil { - // Note: We don't distinguish between resolution-specific errors and generic errors - setInstallStatus(ext, nil) - setResolutionStatus(ext, nil) - setResolvedStatusConditionFailed(ext, err.Error()) - ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonFailed, err.Error()) - return ctrl.Result{}, err - } - - // set deprecation status after _successful_ resolution - // TODO: - // 1. It seems like deprecation status should reflect the currently installed bundle, not the resolved - // bundle. So perhaps we should set package and channel deprecations directly after resolution, but - // defer setting the bundle deprecation until we successfully install the bundle. - // 2. If resolution fails because it can't find a bundle, that doesn't mean we wouldn't be able to find - // a deprecation for the ClusterExtension's spec.packageName. Perhaps we should check for a non-nil - // resolvedDeprecation even if resolution returns an error. If present, we can still update some of - // our deprecation status. - // - Open question though: what if different catalogs have different opinions of what's deprecated. - // If we can't resolve a bundle, how do we know which catalog to trust for deprecation information? - // Perhaps if the package shows up in multiple catalogs and deprecations don't match, we can set - // the deprecation status to unknown? Or perhaps we somehow combine the deprecation information from - // all catalogs? - SetDeprecationStatus(ext, resolvedBundle.Name, resolvedDeprecation) - - resStatus := &ocv1alpha1.ClusterExtensionResolutionStatus{ - Bundle: bundleutil.MetadataFor(resolvedBundle.Name, *resolvedBundleVersion), - } - setResolutionStatus(ext, resStatus) - setResolvedStatusConditionSuccess(ext, fmt.Sprintf("resolved to %q", resolvedBundle.Image)) - - bundleSource := &rukpaksource.BundleSource{ - Name: ext.GetName(), - Type: rukpaksource.SourceTypeImage, - Image: &rukpaksource.ImageSource{ - Ref: resolvedBundle.Image, - }, - } - l.V(1).Info("unpacking resolved bundle") - unpackResult, err := r.Unpacker.Unpack(ctx, bundleSource) - if err != nil { - setStatusUnpackFailed(ext, err.Error()) - return ctrl.Result{}, err - } - - switch unpackResult.State { - case rukpaksource.StatePending: - setStatusUnpackFailed(ext, unpackResult.Message) - ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonFailed, "unpack pending") - return ctrl.Result{}, nil - case rukpaksource.StateUnpacked: - setStatusUnpacked(ext, fmt.Sprintf("unpack successful: %v", unpackResult.Message)) - default: - setStatusUnpackFailed(ext, "unexpected unpack status") - // We previously exit with a failed status if error is not nil. - return ctrl.Result{}, fmt.Errorf("unexpected unpack status: %v", unpackResult.Message) - } - - objLbls := map[string]string{ - labels.OwnerKindKey: ocv1alpha1.ClusterExtensionKind, - labels.OwnerNameKey: ext.GetName(), - } - - storeLbls := map[string]string{ - labels.BundleNameKey: resolvedBundle.Name, - labels.PackageNameKey: resolvedBundle.Package, - labels.BundleVersionKey: resolvedBundleVersion.String(), - } - - l.V(1).Info("applying bundle contents") - // NOTE: We need to be cautious of eating errors here. - // We should always return any error that occurs during an - // attempt to apply content to the cluster. Only when there is - // a verifiable reason to eat the error (i.e it is recoverable) - // should an exception be made. - // The following kinds of errors should be returned up the stack - // to ensure exponential backoff can occur: - // - Permission errors (it is not possible to watch changes to permissions. - // The only way to eventually recover from permission errors is to keep retrying). - managedObjs, _, err := r.Applier.Apply(ctx, unpackResult.Bundle, ext, objLbls, storeLbls) - if err != nil { - setInstalledStatusConditionFailed(ext, err.Error()) - return ctrl.Result{}, err - } - - installStatus := &ocv1alpha1.ClusterExtensionInstallStatus{ - Bundle: bundleutil.MetadataFor(resolvedBundle.Name, *resolvedBundleVersion), - } - setInstallStatus(ext, installStatus) - setInstalledStatusConditionSuccess(ext, fmt.Sprintf("Installed bundle %s successfully", resolvedBundle.Image)) - - l.V(1).Info("watching managed objects") - cache, err := r.Manager.Get(ctx, ext) - if err != nil { - // If we fail to get the cache, set the Healthy condition to - // "Unknown". We can't know the health of resources we can't monitor - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeHealthy, - Reason: ocv1alpha1.ReasonUnverifiable, - Status: metav1.ConditionUnknown, - Message: err.Error(), - ObservedGeneration: ext.Generation, - }) - return ctrl.Result{}, err - } - - if err := cache.Watch(ctx, r.controller, managedObjs...); err != nil { - // If we fail to establish watches, set the Healthy condition to - // "Unknown". We can't know the health of resources we can't monitor - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeHealthy, - Reason: ocv1alpha1.ReasonUnverifiable, - Status: metav1.ConditionUnknown, - Message: err.Error(), - ObservedGeneration: ext.Generation, - }) - return ctrl.Result{}, err - } - - // If we have successfully established the watches, remove the "Healthy" condition. - // It should be interpreted as "Unknown" when not present. - apimeta.RemoveStatusCondition(&ext.Status.Conditions, ocv1alpha1.TypeHealthy) - - return ctrl.Result{}, nil -} - -// SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension -// based on the provided bundle -func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundleName string, deprecation *declcfg.Deprecation) { - deprecations := map[string][]declcfg.DeprecationEntry{} - channelSet := sets.New[string]() - if ext.Spec.Source.Catalog != nil { - for _, channel := range ext.Spec.Source.Catalog.Channels { - channelSet.Insert(channel) - } - } - if deprecation != nil { - for _, entry := range deprecation.Entries { - switch entry.Reference.Schema { - case declcfg.SchemaPackage: - deprecations[ocv1alpha1.TypePackageDeprecated] = []declcfg.DeprecationEntry{entry} - case declcfg.SchemaChannel: - if channelSet.Has(entry.Reference.Name) { - deprecations[ocv1alpha1.TypeChannelDeprecated] = append(deprecations[ocv1alpha1.TypeChannelDeprecated], entry) - } - case declcfg.SchemaBundle: - if bundleName != entry.Reference.Name { - continue - } - deprecations[ocv1alpha1.TypeBundleDeprecated] = []declcfg.DeprecationEntry{entry} - } - } - } - - // first get ordered deprecation messages that we'll join in the Deprecated condition message - var deprecationMessages []string - for _, conditionType := range []string{ - ocv1alpha1.TypePackageDeprecated, - ocv1alpha1.TypeChannelDeprecated, - ocv1alpha1.TypeBundleDeprecated, - } { - if entries, ok := deprecations[conditionType]; ok { - for _, entry := range entries { - deprecationMessages = append(deprecationMessages, entry.Message) - } - } - } - - // next, set the Deprecated condition - status, reason, message := metav1.ConditionFalse, ocv1alpha1.ReasonDeprecated, "" - if len(deprecationMessages) > 0 { - status, reason, message = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated, strings.Join(deprecationMessages, ";") - } - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeDeprecated, - Reason: reason, - Status: status, - Message: message, - ObservedGeneration: ext.Generation, - }) - - // finally, set the individual deprecation conditions for package, channel, and bundle - for _, conditionType := range []string{ - ocv1alpha1.TypePackageDeprecated, - ocv1alpha1.TypeChannelDeprecated, - ocv1alpha1.TypeBundleDeprecated, - } { - entries, ok := deprecations[conditionType] - status, reason, message := metav1.ConditionFalse, ocv1alpha1.ReasonDeprecated, "" - if ok { - status, reason = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated - for _, entry := range entries { - message = fmt.Sprintf("%s\n%s", message, entry.Message) - } - } - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: conditionType, - Reason: reason, - Status: status, - Message: message, - ObservedGeneration: ext.Generation, - }) - } -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error { - controller, err := ctrl.NewControllerManagedBy(mgr). - For(&ocv1alpha1.ClusterExtension{}). - Watches(&catalogd.ClusterCatalog{}, - crhandler.EnqueueRequestsFromMapFunc(clusterExtensionRequestsForCatalog(mgr.GetClient(), mgr.GetLogger())), - builder.WithPredicates(predicate.Funcs{ - UpdateFunc: func(ue event.UpdateEvent) bool { - oldObject, isOldCatalog := ue.ObjectOld.(*catalogd.ClusterCatalog) - newObject, isNewCatalog := ue.ObjectNew.(*catalogd.ClusterCatalog) - - if !isOldCatalog || !isNewCatalog { - return true - } - - if oldObject.Status.ResolvedSource != nil && newObject.Status.ResolvedSource != nil { - if oldObject.Status.ResolvedSource.Image != nil && newObject.Status.ResolvedSource.Image != nil { - return oldObject.Status.ResolvedSource.Image.ResolvedRef != newObject.Status.ResolvedSource.Image.ResolvedRef - } - } - return true - }, - })). - Build(r) - if err != nil { - return err - } - r.controller = controller - r.cache = mgr.GetCache() - - return nil -} - -// Generate reconcile requests for all cluster extensions affected by a catalog change -func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) crhandler.MapFunc { - return func(ctx context.Context, _ client.Object) []reconcile.Request { - // no way of associating an extension to a catalog so create reconcile requests for everything - clusterExtensions := metav1.PartialObjectMetadataList{} - clusterExtensions.SetGroupVersionKind(ocv1alpha1.GroupVersion.WithKind("ClusterExtensionList")) - err := c.List(ctx, &clusterExtensions) - if err != nil { - logger.Error(err, "unable to enqueue cluster extensions for catalog reconcile") - return nil - } - var requests []reconcile.Request - for _, ext := range clusterExtensions.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: ext.GetNamespace(), - Name: ext.GetName(), - }, - }) - } - return requests - } -} - -type DefaultInstalledBundleGetter struct { - helmclient.ActionClientGetter -} - -func (d *DefaultInstalledBundleGetter) GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error) { - cl, err := d.ActionClientFor(ctx, ext) - if err != nil { - return nil, err - } - - release, err := cl.Get(ext.GetName()) - if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { - return nil, err - } - if release == nil { - return nil, nil - } - - return &ocv1alpha1.BundleMetadata{ - Name: release.Labels[labels.BundleNameKey], - Version: release.Labels[labels.BundleVersionKey], - }, nil -} diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go deleted file mode 100644 index 60be87deb5..0000000000 --- a/internal/controllers/clusterextension_controller_test.go +++ /dev/null @@ -1,1399 +0,0 @@ -package controllers_test - -import ( - "context" - "errors" - "fmt" - "testing" - "testing/fstest" - - bsemver "github.com/blang/semver/v4" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/conditionsets" - "github.com/operator-framework/operator-controller/internal/controllers" - "github.com/operator-framework/operator-controller/internal/resolve" - "github.com/operator-framework/operator-controller/internal/rukpak/source" -) - -// Describe: ClusterExtension Controller Test -func TestClusterExtensionDoesNotExist(t *testing.T) { - _, reconciler := newClientAndReconciler(t) - - t.Log("When the cluster extension does not exist") - t.Log("It returns no error") - res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) -} - -func TestClusterExtensionResolutionFails(t *testing.T) { - pkgName := fmt.Sprintf("non-existent-%s", rand.String(6)) - cl, reconciler := newClientAndReconciler(t) - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - return nil, nil, nil, fmt.Errorf("no package %q found", pkgName) - }) - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a non-existent package") - t.Log("By initializing cluster state") - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, - }, - }, - } - require.NoError(t, cl.Create(ctx, clusterExtension)) - - t.Log("It sets resolution failure status") - t.Log("By running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.EqualError(t, err, fmt.Sprintf("no package %q found", pkgName)) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Empty(t, clusterExtension.Status.Resolution) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected conditions") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionFalse, cond.Status) - require.Equal(t, ocv1alpha1.ReasonFailed, cond.Reason) - require.Equal(t, fmt.Sprintf("no package %q found", pkgName), cond.Message) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionResolutionSucceeds(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StatePending, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionFalse, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonFailed, unpackedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionUnpackFails(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - err: errors.New("unpack failure"), - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionFalse, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonFailed, unpackedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionUnpackUnexpectedState(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: "unexpected", - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionFalse, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonFailed, unpackedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionUnpackSucceeds(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StateUnpacked, - Bundle: fstest.MapFS{}, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - reconciler.Applier = &MockApplier{ - err: errors.New("apply failure"), - } - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected resolution conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionTrue, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, unpackedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionInstallationFailedApplierFails(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StateUnpacked, - Bundle: fstest.MapFS{}, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - reconciler.Applier = &MockApplier{ - err: errors.New("apply failure"), - } - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Empty(t, clusterExtension.Status.Install) - - t.Log("By checking the expected resolution conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionTrue, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, unpackedCond.Reason) - - t.Log("By checking the expected installed conditions") - installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, installedCond) - require.Equal(t, metav1.ConditionFalse, installedCond.Status) - require.Equal(t, ocv1alpha1.ReasonFailed, installedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionManagerFailed(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StateUnpacked, - Bundle: fstest.MapFS{}, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - reconciler.Applier = &MockApplier{ - objs: []client.Object{}, - } - reconciler.Manager = &MockManagedContentCacheManager{ - err: errors.New("manager fail"), - } - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) - - t.Log("By checking the expected resolution conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionTrue, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, unpackedCond.Reason) - - t.Log("By checking the expected installed conditions") - installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, installedCond) - require.Equal(t, metav1.ConditionTrue, installedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, installedCond.Reason) - - t.Log("By checking the expected healthy conditions") - managedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeHealthy) - require.NotNil(t, managedCond) - require.Equal(t, metav1.ConditionUnknown, managedCond.Status) - require.Equal(t, ocv1alpha1.ReasonUnverifiable, managedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionManagedContentCacheWatchFail(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StateUnpacked, - Bundle: fstest.MapFS{}, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: ocv1alpha1.SourceTypeCatalog, - - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: installNamespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - reconciler.Applier = &MockApplier{ - objs: []client.Object{}, - } - reconciler.Manager = &MockManagedContentCacheManager{ - cache: &MockManagedContentCache{ - err: errors.New("watch error"), - }, - } - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.Error(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) - - t.Log("By checking the expected resolution conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionTrue, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, unpackedCond.Reason) - - t.Log("By checking the expected installed conditions") - installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, installedCond) - require.Equal(t, metav1.ConditionTrue, installedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, installedCond.Reason) - - t.Log("By checking the expected healthy conditions") - managedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeHealthy) - require.NotNil(t, managedCond) - require.Equal(t, metav1.ConditionUnknown, managedCond.Status) - require.Equal(t, ocv1alpha1.ReasonUnverifiable, managedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func TestClusterExtensionInstallationSucceeds(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - reconciler.Unpacker = &MockUnpacker{ - result: &source.Result{ - State: source.StateUnpacked, - Bundle: fstest.MapFS{}, - }, - } - - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - - t.Log("When the cluster extension specifies a channel with version that exist") - t.Log("By initializing cluster state") - pkgName := "prometheus" - pkgVer := "1.0.0" - pkgChan := "beta" - namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: pkgName, - Version: pkgVer, - Channels: []string{pkgChan}, - }, - }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: serviceAccount, - }, - }, - }, - } - err := cl.Create(ctx, clusterExtension) - require.NoError(t, err) - - t.Log("It sets resolution success status") - t.Log("By running reconcile") - reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1alpha1.ClusterExtension, _ *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - v := bsemver.MustParse("1.0.0") - return &declcfg.Bundle{ - Name: "prometheus.v1.0.0", - Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@fake1.0.0", - }, &v, nil, nil - }) - reconciler.Applier = &MockApplier{ - objs: []client.Object{}, - } - reconciler.Manager = &MockManagedContentCacheManager{ - cache: &MockManagedContentCache{}, - } - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the status fields") - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Resolution.Bundle) - require.Equal(t, ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) - - t.Log("By checking the expected resolution conditions") - resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, resolvedCond) - require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) - - t.Log("By checking the expected unpacked conditions") - unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - require.NotNil(t, unpackedCond) - require.Equal(t, metav1.ConditionTrue, unpackedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, unpackedCond.Reason) - - t.Log("By checking the expected installed conditions") - installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, installedCond) - require.Equal(t, metav1.ConditionTrue, installedCond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, installedCond.Reason) - - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) -} - -func verifyInvariants(ctx context.Context, t *testing.T, c client.Client, ext *ocv1alpha1.ClusterExtension) { - key := client.ObjectKeyFromObject(ext) - require.NoError(t, c.Get(ctx, key, ext)) - - verifyConditionsInvariants(t, ext) -} - -func verifyConditionsInvariants(t *testing.T, ext *ocv1alpha1.ClusterExtension) { - // Expect that the cluster extension's set of conditions contains all defined - // condition types for the ClusterExtension API. Every reconcile should always - // ensure every condition type's status/reason/message reflects the state - // read during _this_ reconcile call. - require.Len(t, ext.Status.Conditions, len(conditionsets.ConditionTypes)) - for _, tt := range conditionsets.ConditionTypes { - cond := apimeta.FindStatusCondition(ext.Status.Conditions, tt) - require.NotNil(t, cond) - require.NotEmpty(t, cond.Status) - require.Contains(t, conditionsets.ConditionReasons, cond.Reason) - require.Equal(t, ext.GetGeneration(), cond.ObservedGeneration) - } -} - -func TestSetDeprecationStatus(t *testing.T) { - for _, tc := range []struct { - name string - clusterExtension *ocv1alpha1.ClusterExtension - expectedClusterExtension *ocv1alpha1.ClusterExtension - bundle *declcfg.Bundle - deprecation *declcfg.Deprecation - }{ - { - name: "no deprecations, all deprecation statuses set to False", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: nil, - }, - { - name: "deprecated channel, but no channel specified, all deprecation statuses set to False", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{}, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{}, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{{ - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - }}, - }, - }, - { - name: "deprecated channel, but a non-deprecated channel specified, all deprecation statuses set to False", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"nondeprecated"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"nondeprecated"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - }, - }, - }, - }, - { - name: "deprecated channel specified, ChannelDeprecated and Deprecated status set to true, others set to false", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - Message: "bad channel!", - }, - }, - }, - }, - { - name: "deprecated package and channel specified, deprecated bundle, all deprecation statuses set to true", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{Name: "badbundle"}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - Message: "bad channel!", - }, - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaPackage, - }, - Message: "bad package!", - }, - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaBundle, - Name: "badbundle", - }, - Message: "bad bundle!", - }, - }, - }, - }, - { - name: "deprecated channel specified, deprecated bundle, all deprecation statuses set to true, all deprecation statuses set to true except PackageDeprecated", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{Name: "badbundle"}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - Message: "bad channel!", - }, - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaBundle, - Name: "badbundle", - }, - Message: "bad bundle!", - }, - }, - }, - }, - { - name: "deprecated package and channel specified, all deprecation statuses set to true except BundleDeprecated", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - Message: "bad channel!", - }, - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaPackage, - }, - Message: "bad package!", - }, - }, - }, - }, - { - name: "deprecated channels specified, ChannelDeprecated and Deprecated status set to true, others set to false", - clusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel", "anotherbadchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{}, - }, - }, - expectedClusterExtension: &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channels: []string{"badchannel", "anotherbadchannel"}, - }, - }, - }, - Status: ocv1alpha1.ClusterExtensionStatus{ - Conditions: []metav1.Condition{ - { - Type: ocv1alpha1.TypeDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypePackageDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeChannelDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionTrue, - ObservedGeneration: 1, - }, - { - Type: ocv1alpha1.TypeBundleDeprecated, - Reason: ocv1alpha1.ReasonDeprecated, - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - }, - }, - }, - }, - bundle: &declcfg.Bundle{}, - deprecation: &declcfg.Deprecation{ - Entries: []declcfg.DeprecationEntry{ - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "badchannel", - }, - Message: "bad channel!", - }, - { - Reference: declcfg.PackageScopedReference{ - Schema: declcfg.SchemaChannel, - Name: "anotherbadchannel", - }, - Message: "another bad channedl!", - }, - }, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - controllers.SetDeprecationStatus(tc.clusterExtension, tc.bundle.Name, tc.deprecation) - // TODO: we should test for unexpected changes to lastTransitionTime. We only expect - // lastTransitionTime to change when the status of the condition changes. - assert.Equal(t, "", cmp.Diff(tc.expectedClusterExtension, tc.clusterExtension, cmpopts.IgnoreFields(metav1.Condition{}, "Message", "LastTransitionTime"))) - }) - } -} diff --git a/internal/controllers/common_controller.go b/internal/controllers/common_controller.go deleted file mode 100644 index 6e94bcc10c..0000000000 --- a/internal/controllers/common_controller.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2023. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" -) - -// setResolvedStatusConditionSuccess sets the resolved status condition to success. -func setResolvedStatusConditionSuccess(ext *ocv1alpha1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeResolved, - Status: metav1.ConditionTrue, - Reason: ocv1alpha1.ReasonSuccess, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -// setResolvedStatusConditionFailed sets the resolved status condition to failed. -func setResolvedStatusConditionFailed(ext *ocv1alpha1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeResolved, - Status: metav1.ConditionFalse, - Reason: ocv1alpha1.ReasonFailed, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -// setInstalledStatusConditionSuccess sets the installed status condition to success. -func setInstalledStatusConditionSuccess(ext *ocv1alpha1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Reason: ocv1alpha1.ReasonSuccess, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -// setInstalledStatusConditionFailed sets the installed status condition to failed. -func setInstalledStatusConditionFailed(ext *ocv1alpha1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeInstalled, - Status: metav1.ConditionFalse, - Reason: ocv1alpha1.ReasonFailed, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -func setStatusUnpackFailed(ext *ocv1alpha1.ClusterExtension, message string) { - setInstallStatus(ext, nil) - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeUnpacked, - Status: metav1.ConditionFalse, - Reason: ocv1alpha1.ReasonFailed, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -func setStatusUnpacked(ext *ocv1alpha1.ClusterExtension, message string) { - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ - Type: ocv1alpha1.TypeUnpacked, - Status: metav1.ConditionTrue, - Reason: ocv1alpha1.ReasonSuccess, - Message: message, - ObservedGeneration: ext.GetGeneration(), - }) -} - -func setResolutionStatus(ext *ocv1alpha1.ClusterExtension, resStatus *ocv1alpha1.ClusterExtensionResolutionStatus) { - ext.Status.Resolution = resStatus -} - -func setInstallStatus(ext *ocv1alpha1.ClusterExtension, installStatus *ocv1alpha1.ClusterExtensionInstallStatus) { - ext.Status.Install = installStatus -} diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go deleted file mode 100644 index 9fa70e4572..0000000000 --- a/internal/controllers/suite_test.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2023. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers_test - -import ( - "context" - "io/fs" - "log" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" - - helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/contentmanager" - cmcache "github.com/operator-framework/operator-controller/internal/contentmanager/cache" - "github.com/operator-framework/operator-controller/internal/controllers" - "github.com/operator-framework/operator-controller/internal/rukpak/source" -) - -// MockUnpacker is a mock of Unpacker interface -type MockUnpacker struct { - err error - result *source.Result -} - -// Unpack mocks the Unpack method -func (m *MockUnpacker) Unpack(_ context.Context, _ *source.BundleSource) (*source.Result, error) { - if m.err != nil { - return nil, m.err - } - return m.result, nil -} - -func (m *MockUnpacker) Cleanup(_ context.Context, _ *source.BundleSource) error { - // TODO implement me - panic("implement me") -} - -func newClient(t *testing.T) client.Client { - // TODO: this is a live client, which behaves differently than a cache client. - // We may want to use a caching client instead to get closer to real behavior. - sch := runtime.NewScheme() - require.NoError(t, ocv1alpha1.AddToScheme(sch)) - cl, err := client.New(config, client.Options{Scheme: sch}) - require.NoError(t, err) - require.NotNil(t, cl) - return cl -} - -type MockInstalledBundleGetter struct { - bundle *ocv1alpha1.BundleMetadata -} - -func (m *MockInstalledBundleGetter) SetBundle(bundle *ocv1alpha1.BundleMetadata) { - m.bundle = bundle -} - -func (m *MockInstalledBundleGetter) GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error) { - return m.bundle, nil -} - -var _ controllers.Applier = (*MockApplier)(nil) - -type MockApplier struct { - err error - objs []client.Object - state string -} - -func (m *MockApplier) Apply(_ context.Context, _ fs.FS, _ *ocv1alpha1.ClusterExtension, _ map[string]string, _ map[string]string) ([]client.Object, string, error) { - if m.err != nil { - return nil, m.state, m.err - } - - return m.objs, m.state, nil -} - -var _ contentmanager.Manager = (*MockManagedContentCacheManager)(nil) - -type MockManagedContentCacheManager struct { - err error - cache cmcache.Cache -} - -func (m *MockManagedContentCacheManager) Get(_ context.Context, _ *ocv1alpha1.ClusterExtension) (cmcache.Cache, error) { - if m.err != nil { - return nil, m.err - } - return m.cache, nil -} - -func (m *MockManagedContentCacheManager) Delete(_ *ocv1alpha1.ClusterExtension) error { - return m.err -} - -type MockManagedContentCache struct { - err error -} - -var _ cmcache.Cache = (*MockManagedContentCache)(nil) - -func (m *MockManagedContentCache) Close() error { - if m.err != nil { - return m.err - } - return nil -} - -func (m *MockManagedContentCache) Watch(_ context.Context, _ cmcache.Watcher, _ ...client.Object) error { - if m.err != nil { - return m.err - } - return nil -} - -func newClientAndReconciler(t *testing.T) (client.Client, *controllers.ClusterExtensionReconciler) { - cl := newClient(t) - - reconciler := &controllers.ClusterExtensionReconciler{ - Client: cl, - InstalledBundleGetter: &MockInstalledBundleGetter{}, - Finalizers: crfinalizer.NewFinalizers(), - } - return cl, reconciler -} - -var ( - config *rest.Config - helmClientGetter helmclient.ActionClientGetter -) - -func TestMain(m *testing.M) { - testEnv := &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "config", "base", "crd", "bases"), - }, - ErrorIfCRDPathMissing: true, - } - - var err error - config, err = testEnv.Start() - utilruntime.Must(err) - if config == nil { - log.Panic("expected cfg to not be nil") - } - - rm := meta.NewDefaultRESTMapper(nil) - cfgGetter, err := helmclient.NewActionConfigGetter(config, rm) - utilruntime.Must(err) - helmClientGetter, err = helmclient.NewActionClientGetter(cfgGetter) - utilruntime.Must(err) - - code := m.Run() - utilruntime.Must(testEnv.Stop()) - os.Exit(code) -} diff --git a/internal/features/features.go b/internal/features/features.go deleted file mode 100644 index e930fbbaac..0000000000 --- a/internal/features/features.go +++ /dev/null @@ -1,28 +0,0 @@ -package features - -import ( - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/component-base/featuregate" -) - -const ( - // Add new feature gates constants (strings) - // Ex: SomeFeature featuregate.Feature = "SomeFeature" - - ForceSemverUpgradeConstraints featuregate.Feature = "ForceSemverUpgradeConstraints" - EnableExtensionAPI featuregate.Feature = "EnableExtensionApi" -) - -var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - // Add new feature gate definitions - // Ex: SomeFeature: {...} - - ForceSemverUpgradeConstraints: {Default: false, PreRelease: featuregate.Alpha}, - EnableExtensionAPI: {Default: false, PreRelease: featuregate.Alpha}, -} - -var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() - -func init() { - utilruntime.Must(OperatorControllerFeatureGate.Add(operatorControllerFeatureGates)) -} diff --git a/internal/httputil/certpoolwatcher.go b/internal/httputil/certpoolwatcher.go deleted file mode 100644 index 2a250d0695..0000000000 --- a/internal/httputil/certpoolwatcher.go +++ /dev/null @@ -1,108 +0,0 @@ -package httputil - -import ( - "crypto/x509" - "fmt" - "os" - "sync" - "time" - - "github.com/fsnotify/fsnotify" - "github.com/go-logr/logr" -) - -type CertPoolWatcher struct { - generation int - dir string - mx sync.RWMutex - pool *x509.CertPool - log logr.Logger - watcher *fsnotify.Watcher - done chan bool -} - -// Returns the current CertPool and the generation number -func (cpw *CertPoolWatcher) Get() (*x509.CertPool, int, error) { - cpw.mx.RLock() - defer cpw.mx.RUnlock() - if cpw.pool == nil { - return nil, 0, fmt.Errorf("no certificate pool available") - } - return cpw.pool.Clone(), cpw.generation, nil -} - -func (cpw *CertPoolWatcher) Done() { - cpw.done <- true -} - -func NewCertPoolWatcher(caDir string, log logr.Logger) (*CertPoolWatcher, error) { - pool, err := NewCertPool(caDir, log) - if err != nil { - return nil, err - } - watcher, err := fsnotify.NewWatcher() - if err != nil { - return nil, err - } - if err = watcher.Add(caDir); err != nil { - return nil, err - } - - cpw := &CertPoolWatcher{ - generation: 1, - dir: caDir, - pool: pool, - log: log, - watcher: watcher, - done: make(chan bool), - } - go func() { - for { - select { - case <-watcher.Events: - cpw.drainEvents() - cpw.update() - case err := <-watcher.Errors: - log.Error(err, "error watching certificate dir") - os.Exit(1) - case <-cpw.done: - err := watcher.Close() - if err != nil { - log.Error(err, "error closing watcher") - } - return - } - } - }() - return cpw, nil -} - -func (cpw *CertPoolWatcher) update() { - cpw.log.Info("updating certificate pool") - pool, err := NewCertPool(cpw.dir, cpw.log) - if err != nil { - cpw.log.Error(err, "error updating certificate pool") - os.Exit(1) - } - cpw.mx.Lock() - defer cpw.mx.Unlock() - cpw.pool = pool - cpw.generation++ -} - -// Drain as many events as possible before doing anything -// Otherwise, we will be hit with an event for _every_ entry in the -// directory, and end up doing an update for each one -func (cpw *CertPoolWatcher) drainEvents() { - for { - drainTimer := time.NewTimer(time.Millisecond * 50) - select { - case <-drainTimer.C: - return - case <-cpw.watcher.Events: - } - if !drainTimer.Stop() { - <-drainTimer.C - } - } -} diff --git a/internal/httputil/certpoolwatcher_test.go b/internal/httputil/certpoolwatcher_test.go deleted file mode 100644 index bfebebd288..0000000000 --- a/internal/httputil/certpoolwatcher_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package httputil_test - -import ( - "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/operator-framework/operator-controller/internal/httputil" -) - -func createCert(t *testing.T, name string) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - require.NoError(t, err) - - notBefore := time.Now() - notAfter := notBefore.Add(time.Hour) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - require.NoError(t, err) - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{name}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - IsCA: true, - - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - - BasicConstraintsValid: true, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - require.NoError(t, err) - - certOut, err := os.Create(name) - require.NoError(t, err) - - err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - require.NoError(t, err) - - err = certOut.Close() - require.NoError(t, err) - - // ignore the key -} - -func TestCertPoolWatcher(t *testing.T) { - // create a temporary directory - tmpDir, err := os.MkdirTemp("", "cert-pool") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // create the first cert - certName := filepath.Join(tmpDir, "test1.pem") - t.Logf("Create cert file at %q\n", certName) - createCert(t, certName) - - // Create the cert pool watcher - cpw, err := httputil.NewCertPoolWatcher(tmpDir, log.FromContext(context.Background())) - require.NoError(t, err) - defer cpw.Done() - - // Get the original pool - firstPool, firstGen, err := cpw.Get() - require.NoError(t, err) - require.NotNil(t, firstPool) - - // Create a second cert - certName = filepath.Join(tmpDir, "test2.pem") - t.Logf("Create cert file at %q\n", certName) - createCert(t, certName) - - require.Eventually(t, func() bool { - secondPool, secondGen, err := cpw.Get() - if err != nil { - return false - } - return secondGen != firstGen && !firstPool.Equal(secondPool) - }, 30*time.Second, time.Second) -} diff --git a/internal/httputil/certutil.go b/internal/httputil/certutil.go deleted file mode 100644 index 767fd57a64..0000000000 --- a/internal/httputil/certutil.go +++ /dev/null @@ -1,248 +0,0 @@ -package httputil - -import ( - "bytes" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/go-logr/logr" -) - -var pemStart = []byte("\n-----BEGIN ") -var pemEnd = []byte("\n-----END ") -var pemEndOfLine = []byte("-----") -var colon = []byte(":") - -// getLine results the first \r\n or \n delineated line from the given byte -// array. The line does not include trailing whitespace or the trailing new -// line bytes. The remainder of the byte array (also not including the new line -// bytes) is also returned and this will always be smaller than the original -// argument. -func getLine(data []byte) ([]byte, []byte) { - i := bytes.IndexByte(data, '\n') - var j int - if i < 0 { - i = len(data) - j = i - } else { - j = i + 1 - if i > 0 && data[i-1] == '\r' { - i-- - } - } - return bytes.TrimRight(data[0:i], " \t"), data[j:] -} - -// removeSpacesAndTabs returns a copy of its input with all spaces and tabs -// removed, if there were any. Otherwise, the input is returned unchanged. -// -// The base64 decoder already skips newline characters, so we don't need to -// filter them out here. -func removeSpacesAndTabs(data []byte) []byte { - if !bytes.ContainsAny(data, " \t") { - // Fast path; most base64 data within PEM contains newlines, but - // no spaces nor tabs. Skip the extra alloc and work. - return data - } - result := make([]byte, len(data)) - n := 0 - - for _, b := range data { - if b == ' ' || b == '\t' { - continue - } - result[n] = b - n++ - } - - return result[0:n] -} - -// This version of pem.Decode() is a bit less flexible, it will not skip over bad PEM -// It is basically the guts of pem.Decode() inside the outer for loop, with error -// returns rather than continues -func pemDecode(data []byte) (*pem.Block, []byte) { - // pemStart begins with a newline. However, at the very beginning of - // the byte array, we'll accept the start string without it. - rest := data - if bytes.HasPrefix(rest, pemStart[1:]) { - rest = rest[len(pemStart)-1:] - } else if _, after, ok := bytes.Cut(rest, pemStart); ok { - rest = after - } else { - return nil, data - } - - var typeLine []byte - typeLine, rest = getLine(rest) - if !bytes.HasSuffix(typeLine, pemEndOfLine) { - return nil, data - } - typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] - - p := &pem.Block{ - Headers: make(map[string]string), - Type: string(typeLine), - } - - for { - // This loop terminates because getLine's second result is - // always smaller than its argument. - if len(rest) == 0 { - return nil, data - } - line, next := getLine(rest) - - key, val, ok := bytes.Cut(line, colon) - if !ok { - break - } - - key = bytes.TrimSpace(key) - val = bytes.TrimSpace(val) - p.Headers[string(key)] = string(val) - rest = next - } - - var endIndex, endTrailerIndex int - - // If there were no headers, the END line might occur - // immediately, without a leading newline. - if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) { - endIndex = 0 - endTrailerIndex = len(pemEnd) - 1 - } else { - endIndex = bytes.Index(rest, pemEnd) - endTrailerIndex = endIndex + len(pemEnd) - } - - if endIndex < 0 { - return nil, data - } - - // After the "-----" of the ending line, there should be the same type - // and then a final five dashes. - endTrailer := rest[endTrailerIndex:] - endTrailerLen := len(typeLine) + len(pemEndOfLine) - if len(endTrailer) < endTrailerLen { - return nil, data - } - - restOfEndLine := endTrailer[endTrailerLen:] - endTrailer = endTrailer[:endTrailerLen] - if !bytes.HasPrefix(endTrailer, typeLine) || - !bytes.HasSuffix(endTrailer, pemEndOfLine) { - return nil, data - } - - // The line must end with only whitespace. - if s, _ := getLine(restOfEndLine); len(s) != 0 { - return nil, data - } - - base64Data := removeSpacesAndTabs(rest[:endIndex]) - p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) - n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) - if err != nil { - return nil, data - } - p.Bytes = p.Bytes[:n] - - // the -1 is because we might have only matched pemEnd without the - // leading newline if the PEM block was empty. - _, rest = getLine(rest[endIndex+len(pemEnd)-1:]) - return p, rest -} - -// This version of (*x509.CertPool).AppendCertsFromPEM() will error out if parsing fails -func appendCertsFromPEM(s *x509.CertPool, pemCerts []byte, firstExpiration *time.Time) error { - n := 1 - for len(pemCerts) > 0 { - var block *pem.Block - block, pemCerts = pemDecode(pemCerts) - if block == nil { - return fmt.Errorf("unable to PEM decode cert %d", n) - } - // ignore non-certificates (e.g. keys) - if block.Type != "CERTIFICATE" { - continue - } - if len(block.Headers) != 0 { - // This is a cert, but we're ignoring it, so bump the counter - n++ - continue - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("unable to parse cert %d: %w", n, err) - } - if firstExpiration.IsZero() || firstExpiration.After(cert.NotAfter) { - *firstExpiration = cert.NotAfter - } - now := time.Now() - if now.Before(cert.NotBefore) { - return fmt.Errorf("not yet valid cert %d: %q", n, cert.NotBefore.Format(time.RFC3339)) - } else if now.After(cert.NotAfter) { - return fmt.Errorf("expired cert %d: %q", n, cert.NotAfter.Format(time.RFC3339)) - } - // no return values - panics or always succeeds - s.AddCert(cert) - n++ - } - - return nil -} - -func NewCertPool(caDir string, log logr.Logger) (*x509.CertPool, error) { - caCertPool, err := x509.SystemCertPool() - if err != nil { - return nil, err - } - if caDir == "" { - return caCertPool, nil - } - - dirEntries, err := os.ReadDir(caDir) - if err != nil { - return nil, err - } - count := 0 - firstExpiration := time.Time{} - - for _, e := range dirEntries { - file := filepath.Join(caDir, e.Name()) - // These might be symlinks pointing to directories, so use Stat() to resolve - fi, err := os.Stat(file) - if err != nil { - return nil, err - } - if fi.IsDir() { - log.Info("skip directory", "name", e.Name()) - continue - } - log.Info("load certificate", "name", e.Name()) - data, err := os.ReadFile(file) - if err != nil { - return nil, fmt.Errorf("error reading cert file %q: %w", file, err) - } - err = appendCertsFromPEM(caCertPool, data, &firstExpiration) - if err != nil { - return nil, fmt.Errorf("error adding cert file %q: %w", file, err) - } - count++ - } - - // Found no certs! - if count == 0 { - return nil, fmt.Errorf("no certificates found in %q", caDir) - } - - log.Info("first expiration", "time", firstExpiration.Format(time.RFC3339)) - return caCertPool, nil -} diff --git a/internal/httputil/certutil_test.go b/internal/httputil/certutil_test.go deleted file mode 100644 index a8a158ff37..0000000000 --- a/internal/httputil/certutil_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package httputil_test - -import ( - "context" - "testing" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-controller/internal/httputil" -) - -// The "good" test consists of 3 Amazon Root CAs, along with a "PRIVATE KEY" in one of the files -// The "bad" test consists of 2 Amazon Root CAs, the second of which is garbage, and the test fails -// The "ugly" test consists of a single file: -// - Amazon_Root_CA_1 -// - garbage PEM -// - Amazon_Root_CA_3 -// The error is _not_ detected because the golang standard library PEM decoder skips right over the garbage -// This demonstrates the danger of putting multiple certificates into a single file -func TestNewCertPool(t *testing.T) { - caDirs := []struct { - dir string - msg string - }{ - {"../../testdata/certs/", `no certificates found in "../../testdata/certs/"`}, - {"../../testdata/certs/good", ""}, - {"../../testdata/certs/bad", `error adding cert file "../../testdata/certs/bad/Amazon_Root_CA_2.pem": unable to PEM decode cert 1`}, - {"../../testdata/certs/ugly", `error adding cert file "../../testdata/certs/ugly/Amazon_Root_CA.pem": unable to PEM decode cert 2`}, - {"../../testdata/certs/ugly2", `error adding cert file "../../testdata/certs/ugly2/Amazon_Root_CA_1.pem": unable to PEM decode cert 1`}, - {"../../testdata/certs/ugly3", `error adding cert file "../../testdata/certs/ugly3/not_a_cert.pem": unable to PEM decode cert 1`}, - {"../../testdata/certs/empty", `error adding cert file "../../testdata/certs/empty/empty.pem": unable to parse cert 1: x509: malformed certificate`}, - {"../../testdata/certs/expired", `error adding cert file "../../testdata/certs/expired/expired.pem": expired cert 1: "2024-01-02T15:00:00Z"`}, - } - - log, _ := logr.FromContext(context.Background()) - for _, caDir := range caDirs { - t.Logf("Loading certs from %q", caDir.dir) - pool, err := httputil.NewCertPool(caDir.dir, log) - if caDir.msg == "" { - require.NoError(t, err) - require.NotNil(t, pool) - } else { - require.Error(t, err) - require.Nil(t, pool) - require.ErrorContains(t, err, caDir.msg) - } - } -} diff --git a/internal/labels/labels.go b/internal/labels/labels.go deleted file mode 100644 index 59061f339a..0000000000 --- a/internal/labels/labels.go +++ /dev/null @@ -1,9 +0,0 @@ -package labels - -const ( - OwnerKindKey = "olm.operatorframework.io/owner-kind" - OwnerNameKey = "olm.operatorframework.io/owner-name" - PackageNameKey = "olm.operatorframework.io/package-name" - BundleNameKey = "olm.operatorframework.io/bundle-name" - BundleVersionKey = "olm.operatorframework.io/bundle-version" -) diff --git a/internal/operator-controller/OWNERS b/internal/operator-controller/OWNERS new file mode 100644 index 0000000000..3174715ba6 --- /dev/null +++ b/internal/operator-controller/OWNERS @@ -0,0 +1,2 @@ +approvers: + - operator-controller-approvers diff --git a/internal/action/error/errors.go b/internal/operator-controller/action/error/errors.go similarity index 100% rename from internal/action/error/errors.go rename to internal/operator-controller/action/error/errors.go diff --git a/internal/action/error/errors_test.go b/internal/operator-controller/action/error/errors_test.go similarity index 100% rename from internal/action/error/errors_test.go rename to internal/operator-controller/action/error/errors_test.go diff --git a/internal/action/helm.go b/internal/operator-controller/action/helm.go similarity index 90% rename from internal/action/helm.go rename to internal/operator-controller/action/helm.go index 0ac18cf794..671a56b1b2 100644 --- a/internal/action/helm.go +++ b/internal/operator-controller/action/helm.go @@ -9,7 +9,7 @@ import ( actionclient "github.com/operator-framework/helm-operator-plugins/pkg/client" - olmv1error "github.com/operator-framework/operator-controller/internal/action/error" + olmv1error "github.com/operator-framework/operator-controller/internal/operator-controller/action/error" ) type ActionClientGetter struct { @@ -75,6 +75,12 @@ func (a ActionClient) Get(name string, opts ...actionclient.GetOption) (*release return resp, err } +func (a ActionClient) History(name string, opts ...actionclient.HistoryOption) ([]*release.Release, error) { + resp, err := a.ActionInterface.History(name, opts...) + err = a.actionClientErrorTranslator(err) + return resp, err +} + func (a ActionClient) Reconcile(rel *release.Release) error { return a.actionClientErrorTranslator(a.ActionInterface.Reconcile(rel)) } diff --git a/internal/action/helm_test.go b/internal/operator-controller/action/helm_test.go similarity index 88% rename from internal/action/helm_test.go rename to internal/operator-controller/action/helm_test.go index 8979f18311..3f1f02f9db 100644 --- a/internal/action/helm_test.go +++ b/internal/operator-controller/action/helm_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,6 +30,17 @@ func (m *mockActionClient) Get(name string, opts ...actionclient.GetOption) (*re return args.Get(0).(*release.Release), args.Error(1) } +func (m *mockActionClient) History(name string, opts ...actionclient.HistoryOption) ([]*release.Release, error) { + args := m.Called(name, opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + rel := []*release.Release{ + args.Get(0).(*release.Release), + } + return rel, args.Error(1) +} + func (m *mockActionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...actionclient.InstallOption) (*release.Release, error) { args := m.Called(name, namespace, chrt, vals, opts) if args.Get(0) == nil { @@ -81,6 +93,7 @@ func TestActionClientErrorTranslation(t *testing.T) { ac := new(mockActionClient) ac.On("Get", mock.Anything, mock.Anything).Return(nil, originalError) + ac.On("History", mock.Anything, mock.Anything).Return(nil, originalError) ac.On("Install", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, originalError) ac.On("Uninstall", mock.Anything, mock.Anything).Return(nil, originalError) ac.On("Upgrade", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, originalError) @@ -92,6 +105,10 @@ func TestActionClientErrorTranslation(t *testing.T) { _, err := wrappedAc.Get("something") assert.Equal(t, expectedErr, err, "expected Get() to return translated error") + // History + _, err = wrappedAc.History("something") + assert.Equal(t, expectedErr, err, "expected History() to return translated error") + // Install _, err = wrappedAc.Install("something", "somethingelse", nil, nil) assert.Equal(t, expectedErr, err, "expected Install() to return translated error") @@ -130,12 +147,12 @@ func TestActionClientFor(t *testing.T) { // Test the successful case actionClient, err := acg.ActionClientFor(ctx, obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, actionClient) assert.IsType(t, &ActionClient{}, actionClient) // Test the error case actionClient, err = acg.ActionClientFor(ctx, obj) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, actionClient) } diff --git a/internal/operator-controller/action/restconfig.go b/internal/operator-controller/action/restconfig.go new file mode 100644 index 0000000000..05e25f707d --- /dev/null +++ b/internal/operator-controller/action/restconfig.go @@ -0,0 +1,74 @@ +package action + +import ( + "context" + "fmt" + "net/http" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/transport" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" +) + +const syntheticServiceAccountName = "olm.synthetic-user" + +// SyntheticUserRestConfigMapper returns an AuthConfigMapper that that impersonates synthetic users and groups for Object o. +// o is expected to be a ClusterExtension. If the service account defined in o is different from 'olm.synthetic-user', the +// defaultAuthMapper will be used +func SyntheticUserRestConfigMapper(defaultAuthMapper func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error)) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + cExt, err := validate(o, c) + if err != nil { + return nil, err + } + if cExt.Spec.ServiceAccount.Name != syntheticServiceAccountName { + return defaultAuthMapper(ctx, cExt, c) + } + cc := rest.CopyConfig(c) + cc.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return transport.NewImpersonatingRoundTripper(authentication.SyntheticImpersonationConfig(*cExt), rt) + }) + return cc, nil + } +} + +// ServiceAccountRestConfigMapper returns an AuthConfigMapper scoped to the service account defined in o, which is expected to +// be a ClusterExtension +func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + cExt, err := validate(o, c) + if err != nil { + return nil, err + } + saConfig := rest.AnonymousClientConfig(c) + saConfig.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &authentication.TokenInjectingRoundTripper{ + Tripper: rt, + TokenGetter: tokenGetter, + Key: types.NamespacedName{ + Name: cExt.Spec.ServiceAccount.Name, + Namespace: cExt.Spec.Namespace, + }, + } + }) + return saConfig, nil + } +} + +func validate(o client.Object, c *rest.Config) (*ocv1.ClusterExtension, error) { + if c == nil { + return nil, fmt.Errorf("rest config is nil") + } + if o == nil { + return nil, fmt.Errorf("object is nil") + } + cExt, ok := o.(*ocv1.ClusterExtension) + if !ok { + return nil, fmt.Errorf("object is not a ClusterExtension") + } + return cExt, nil +} diff --git a/internal/operator-controller/action/restconfig_test.go b/internal/operator-controller/action/restconfig_test.go new file mode 100644 index 0000000000..4c9f786719 --- /dev/null +++ b/internal/operator-controller/action/restconfig_test.go @@ -0,0 +1,177 @@ +package action_test + +import ( + "context" + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/action" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" +) + +func Test_ServiceAccountRestConfigMapper(t *testing.T) { + for _, tc := range []struct { + description string + obj client.Object + cfg *rest.Config + expectedError error + }{ + { + description: "return error if object is nil", + cfg: &rest.Config{}, + expectedError: errors.New("object is nil"), + }, { + description: "return error if cfg is nil", + obj: &ocv1.ClusterExtension{}, + expectedError: errors.New("rest config is nil"), + }, { + description: "return error if object is not a ClusterExtension", + obj: &corev1.Secret{}, + cfg: &rest.Config{}, + expectedError: errors.New("object is not a ClusterExtension"), + }, { + description: "succeeds if object is not a ClusterExtension", + obj: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-clusterextension", + }, + Spec: ocv1.ClusterExtensionSpec{ + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "my-service-account", + }, + Namespace: "my-namespace", + }, + }, + cfg: &rest.Config{}, + }, + } { + t.Run(tc.description, func(t *testing.T) { + tokenGetter := &authentication.TokenGetter{} + saMapper := action.ServiceAccountRestConfigMapper(tokenGetter) + actualCfg, err := saMapper(context.Background(), tc.obj, tc.cfg) + if tc.expectedError != nil { + require.Nil(t, actualCfg) + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + transport, err := rest.TransportFor(actualCfg) + require.NoError(t, err) + require.NotNil(t, transport) + tokenInjectionRoundTripper, ok := transport.(*authentication.TokenInjectingRoundTripper) + require.True(t, ok) + require.Equal(t, tokenGetter, tokenInjectionRoundTripper.TokenGetter) + require.Equal(t, types.NamespacedName{Name: "my-service-account", Namespace: "my-namespace"}, tokenInjectionRoundTripper.Key) + } + }) + } +} + +func Test_SyntheticUserRestConfigMapper_Fails(t *testing.T) { + for _, tc := range []struct { + description string + obj client.Object + cfg *rest.Config + expectedError error + }{ + { + description: "return error if object is nil", + cfg: &rest.Config{}, + expectedError: errors.New("object is nil"), + }, { + description: "return error if cfg is nil", + obj: &ocv1.ClusterExtension{}, + expectedError: errors.New("rest config is nil"), + }, { + description: "return error if object is not a ClusterExtension", + obj: &corev1.Secret{}, + cfg: &rest.Config{}, + expectedError: errors.New("object is not a ClusterExtension"), + }, + } { + t.Run(tc.description, func(t *testing.T) { + tokenGetter := &authentication.TokenGetter{} + saMapper := action.ServiceAccountRestConfigMapper(tokenGetter) + actualCfg, err := saMapper(context.Background(), tc.obj, tc.cfg) + if tc.expectedError != nil { + require.Nil(t, actualCfg) + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + transport, err := rest.TransportFor(actualCfg) + require.NoError(t, err) + require.NotNil(t, transport) + tokenInjectionRoundTripper, ok := transport.(*authentication.TokenInjectingRoundTripper) + require.True(t, ok) + require.Equal(t, tokenGetter, tokenInjectionRoundTripper.TokenGetter) + require.Equal(t, types.NamespacedName{Name: "my-service-account", Namespace: "my-namespace"}, tokenInjectionRoundTripper.Key) + } + }) + } +} +func Test_SyntheticUserRestConfigMapper_UsesDefaultConfigMapper(t *testing.T) { + isDefaultRequestMapperUsed := false + defaultServiceMapper := func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + isDefaultRequestMapperUsed = true + return c, nil + } + syntheticAuthServiceMapper := action.SyntheticUserRestConfigMapper(defaultServiceMapper) + obj := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-clusterextension", + }, + Spec: ocv1.ClusterExtensionSpec{ + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "my-service-account", + }, + Namespace: "my-namespace", + }, + } + actualCfg, err := syntheticAuthServiceMapper(context.Background(), obj, &rest.Config{}) + require.NoError(t, err) + require.NotNil(t, actualCfg) + require.True(t, isDefaultRequestMapperUsed) +} + +func Test_SyntheticUserRestConfigMapper_UsesSyntheticAuthMapper(t *testing.T) { + syntheticAuthServiceMapper := action.SyntheticUserRestConfigMapper(func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + return c, nil + }) + obj := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-clusterextension", + }, + Spec: ocv1.ClusterExtensionSpec{ + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "olm.synthetic-user", + }, + Namespace: "my-namespace", + }, + } + actualCfg, err := syntheticAuthServiceMapper(context.Background(), obj, &rest.Config{}) + require.NoError(t, err) + require.NotNil(t, actualCfg) + + // test that the impersonation headers are appropriately injected into the request + // by wrapping a fake round tripper around the returned configurations transport + // nolint:bodyclose + _, _ = actualCfg.WrapTransport(fakeRoundTripper(func(req *http.Request) (*http.Response, error) { + require.Equal(t, "olm:clusterextension:my-clusterextension", req.Header.Get("Impersonate-User")) + require.Equal(t, "olm:clusterextensions", req.Header.Get("Impersonate-Group")) + return &http.Response{}, nil + })).RoundTrip(&http.Request{}) +} + +type fakeRoundTripper func(req *http.Request) (*http.Response, error) + +func (f fakeRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + return f(request) +} diff --git a/internal/action/storagedriver.go b/internal/operator-controller/action/storagedriver.go similarity index 100% rename from internal/action/storagedriver.go rename to internal/operator-controller/action/storagedriver.go diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go new file mode 100644 index 0000000000..fa3f85e790 --- /dev/null +++ b/internal/operator-controller/applier/boxcutter.go @@ -0,0 +1,384 @@ +package applier + +import ( + "cmp" + "context" + "errors" + "fmt" + "io/fs" + "maps" + "slices" + "strings" + + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/yaml" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/internal/operator-controller/labels" +) + +const ( + ClusterExtensionRevisionPreviousLimit = 5 +) + +type ClusterExtensionRevisionGenerator interface { + GenerateRevision(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) + GenerateRevisionFromHelmRelease( + helmRelease *release.Release, ext *ocv1.ClusterExtension, + objectLabels map[string]string, + ) (*ocv1.ClusterExtensionRevision, error) +} + +type SimpleRevisionGenerator struct { + Scheme *runtime.Scheme + ManifestProvider ManifestProvider +} + +func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( + helmRelease *release.Release, ext *ocv1.ClusterExtension, + objectLabels map[string]string, +) (*ocv1.ClusterExtensionRevision, error) { + docs := splitManifestDocuments(helmRelease.Manifest) + objs := make([]ocv1.ClusterExtensionRevisionObject, 0, len(docs)) + for _, doc := range docs { + obj := unstructured.Unstructured{} + if err := yaml.Unmarshal([]byte(doc), &obj); err != nil { + return nil, err + } + + labels := maps.Clone(obj.GetLabels()) + if labels == nil { + labels = map[string]string{} + } + maps.Copy(labels, objectLabels) + obj.SetLabels(labels) + obj.SetOwnerReferences(nil) // reset OwnerReferences for migration. + + objs = append(objs, ocv1.ClusterExtensionRevisionObject{ + Object: obj, + CollisionProtection: ocv1.CollisionProtectionNone, // allow to adopt objects from previous release + }) + } + + rev := r.buildClusterExtensionRevision(objs, ext, map[string]string{ + labels.BundleNameKey: helmRelease.Labels[labels.BundleNameKey], + labels.PackageNameKey: helmRelease.Labels[labels.PackageNameKey], + labels.BundleVersionKey: helmRelease.Labels[labels.BundleVersionKey], + labels.BundleReferenceKey: helmRelease.Labels[labels.BundleReferenceKey], + }) + rev.Name = fmt.Sprintf("%s-1", ext.Name) + rev.Spec.Revision = 1 + return rev, nil +} + +func (r *SimpleRevisionGenerator) GenerateRevision( + bundleFS fs.FS, ext *ocv1.ClusterExtension, + objectLabels, revisionAnnotations map[string]string, +) (*ocv1.ClusterExtensionRevision, error) { + // extract plain manifests + plain, err := r.ManifestProvider.Get(bundleFS, ext) + if err != nil { + return nil, err + } + + // objectLabels + objs := make([]ocv1.ClusterExtensionRevisionObject, 0, len(plain)) + for _, obj := range plain { + labels := maps.Clone(obj.GetLabels()) + if labels == nil { + labels = map[string]string{} + } + maps.Copy(labels, objectLabels) + obj.SetLabels(labels) + + gvk, err := apiutil.GVKForObject(obj, r.Scheme) + if err != nil { + return nil, err + } + + unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, err + } + unstr := unstructured.Unstructured{Object: unstrObj} + unstr.SetGroupVersionKind(gvk) + + objs = append(objs, ocv1.ClusterExtensionRevisionObject{ + Object: unstr, + }) + } + + if revisionAnnotations == nil { + revisionAnnotations = map[string]string{} + } + + return r.buildClusterExtensionRevision(objs, ext, revisionAnnotations), nil +} + +func (r *SimpleRevisionGenerator) buildClusterExtensionRevision( + objects []ocv1.ClusterExtensionRevisionObject, + ext *ocv1.ClusterExtension, + annotations map[string]string, +) *ocv1.ClusterExtensionRevision { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: annotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Phases: PhaseSort(objects), + }, + } +} + +type BoxcutterStorageMigrator struct { + ActionClientGetter helmclient.ActionClientGetter + RevisionGenerator ClusterExtensionRevisionGenerator + Client boxcutterStorageMigratorClient +} + +type boxcutterStorageMigratorClient interface { + List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error + Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error +} + +func (m *BoxcutterStorageMigrator) Migrate(ctx context.Context, ext *ocv1.ClusterExtension, objectLabels map[string]string) error { + existingRevisionList := ocv1.ClusterExtensionRevisionList{} + if err := m.Client.List(ctx, &existingRevisionList, client.MatchingLabels{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }); err != nil { + return fmt.Errorf("listing ClusterExtensionRevisions before attempting migration: %w", err) + } + if len(existingRevisionList.Items) != 0 { + // No migration needed. + return nil + } + + ac, err := m.ActionClientGetter.ActionClientFor(ctx, ext) + if err != nil { + return err + } + + helmRelease, err := ac.Get(ext.GetName()) + if errors.Is(err, driver.ErrReleaseNotFound) { + // no Helm Release -> no prior installation. + return nil + } + if err != nil { + return err + } + + rev, err := m.RevisionGenerator.GenerateRevisionFromHelmRelease(helmRelease, ext, objectLabels) + if err != nil { + return err + } + + if err := m.Client.Create(ctx, rev); err != nil { + return err + } + return nil +} + +type Boxcutter struct { + Client client.Client + Scheme *runtime.Scheme + RevisionGenerator ClusterExtensionRevisionGenerator + Preflights []Preflight + FieldOwner string +} + +func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) { + return bc.apply(ctx, contentFS, ext, objectLabels, revisionAnnotations) +} + +func (bc *Boxcutter) getObjects(rev *ocv1.ClusterExtensionRevision) []client.Object { + var objs []client.Object + for _, phase := range rev.Spec.Phases { + for _, phaseObject := range phase.Objects { + objs = append(objs, &phaseObject.Object) + } + } + return objs +} + +func (bc *Boxcutter) createOrUpdate(ctx context.Context, obj client.Object) error { + if obj.GetObjectKind().GroupVersionKind().Empty() { + gvk, err := apiutil.GVKForObject(obj, bc.Scheme) + if err != nil { + return err + } + obj.GetObjectKind().SetGroupVersionKind(gvk) + } + return bc.Client.Patch(ctx, obj, client.Apply, client.FieldOwner(bc.FieldOwner), client.ForceOwnership) +} + +func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) { + // Generate desired revision + desiredRevision, err := bc.RevisionGenerator.GenerateRevision(contentFS, ext, objectLabels, revisionAnnotations) + if err != nil { + return false, "", err + } + + if err := controllerutil.SetControllerReference(ext, desiredRevision, bc.Scheme); err != nil { + return false, "", fmt.Errorf("set ownerref: %w", err) + } + + // List all existing revisions + existingRevisions, err := bc.getExistingRevisions(ctx, ext.GetName()) + if err != nil { + return false, "", err + } + + currentRevision := &ocv1.ClusterExtensionRevision{} + state := StateNeedsInstall + // check if we can update the current revision. + if len(existingRevisions) > 0 { + // try first to update the current revision. + currentRevision = &existingRevisions[len(existingRevisions)-1] + desiredRevision.Spec.Previous = currentRevision.Spec.Previous + desiredRevision.Spec.Revision = currentRevision.Spec.Revision + desiredRevision.Name = currentRevision.Name + + err := bc.createOrUpdate(ctx, desiredRevision) + switch { + case apierrors.IsInvalid(err): + // We could not update the current revision due to trying to update an immutable field. + // Therefore, we need to create a new revision. + state = StateNeedsUpgrade + case err == nil: + // inplace patch was successful, no changes in phases + state = StateUnchanged + default: + return false, "", fmt.Errorf("patching %s Revision: %w", desiredRevision.Name, err) + } + } + + // Preflights + plainObjs := bc.getObjects(desiredRevision) + for _, preflight := range bc.Preflights { + if shouldSkipPreflight(ctx, preflight, ext, state) { + continue + } + switch state { + case StateNeedsInstall: + err := preflight.Install(ctx, plainObjs) + if err != nil { + return false, "", err + } + // TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but + // it isn't immediately obvious why that is. Perhaps len(existingRevisions) is + // always greater than 0 (seems unlikely), or shouldSkipPreflight always returns + // true (and we continue) when state == StateNeedsInstall? + case StateNeedsUpgrade: + err := preflight.Upgrade(ctx, plainObjs) + if err != nil { + return false, "", err + } + } + } + + if state != StateUnchanged { + // need to create new revision + prevRevisions := existingRevisions + revisionNumber := latestRevisionNumber(prevRevisions) + 1 + + desiredRevision.Name = fmt.Sprintf("%s-%d", ext.Name, revisionNumber) + desiredRevision.Spec.Revision = revisionNumber + + if err = bc.setPreviousRevisions(ctx, desiredRevision, prevRevisions); err != nil { + return false, "", fmt.Errorf("garbage collecting old Revisions: %w", err) + } + + if err := bc.createOrUpdate(ctx, desiredRevision); err != nil { + return false, "", fmt.Errorf("creating new Revision: %w", err) + } + currentRevision = desiredRevision + } + + progressingCondition := meta.FindStatusCondition(currentRevision.Status.Conditions, ocv1.TypeProgressing) + availableCondition := meta.FindStatusCondition(currentRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) + succeededCondition := meta.FindStatusCondition(currentRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) + + if progressingCondition == nil && availableCondition == nil && succeededCondition == nil { + return false, "New revision created", nil + } else if progressingCondition != nil && progressingCondition.Status == metav1.ConditionTrue { + return false, progressingCondition.Message, nil + } else if availableCondition != nil && availableCondition.Status != metav1.ConditionTrue { + return false, "", errors.New(availableCondition.Message) + } else if succeededCondition != nil && succeededCondition.Status != metav1.ConditionTrue { + return false, succeededCondition.Message, nil + } + return true, "", nil +} + +// setPreviousRevisions populates spec.previous of latestRevision, trimming the list of previous _archived_ revisions down to +// ClusterExtensionRevisionPreviousLimit or to the first _active_ revision and deletes trimmed revisions from the cluster. +// NOTE: revisionList must be sorted in chronographical order, from oldest to latest. +func (bc *Boxcutter) setPreviousRevisions(ctx context.Context, latestRevision *ocv1.ClusterExtensionRevision, revisionList []ocv1.ClusterExtensionRevision) error { + trimmedPrevious := make([]ocv1.ClusterExtensionRevisionPrevious, 0) + for index, r := range revisionList { + if index < len(revisionList)-ClusterExtensionRevisionPreviousLimit && r.Spec.LifecycleState == ocv1.ClusterExtensionRevisionLifecycleStateArchived { + // Delete oldest CREs from the cluster and list to reach ClusterExtensionRevisionPreviousLimit or latest active revision + if err := bc.Client.Delete(ctx, &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name, + }, + }); err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("deleting previous archived Revision: %w", err) + } + } else { + // All revisions within the limit or still active are preserved + trimmedPrevious = append(trimmedPrevious, ocv1.ClusterExtensionRevisionPrevious{Name: r.Name, UID: r.GetUID()}) + } + } + latestRevision.Spec.Previous = trimmedPrevious + return nil +} + +// getExistingRevisions returns the list of ClusterExtensionRevisions for a ClusterExtension with name extName in revision order (oldest to newest) +func (bc *Boxcutter) getExistingRevisions(ctx context.Context, extName string) ([]ocv1.ClusterExtensionRevision, error) { + existingRevisionList := &ocv1.ClusterExtensionRevisionList{} + if err := bc.Client.List(ctx, existingRevisionList, client.MatchingLabels{ + controllers.ClusterExtensionRevisionOwnerLabel: extName, + }); err != nil { + return nil, fmt.Errorf("listing revisions: %w", err) + } + slices.SortFunc(existingRevisionList.Items, func(a, b ocv1.ClusterExtensionRevision) int { + return cmp.Compare(a.Spec.Revision, b.Spec.Revision) + }) + return existingRevisionList.Items, nil +} + +func latestRevisionNumber(prevRevisions []ocv1.ClusterExtensionRevision) int64 { + if len(prevRevisions) == 0 { + return 0 + } + return prevRevisions[len(prevRevisions)-1].Spec.Revision +} + +func splitManifestDocuments(file string) []string { + //nolint:prealloc + var docs []string + for _, manifest := range strings.Split(file, "\n") { + manifest = strings.TrimSpace(manifest) + if len(manifest) == 0 { + continue + } + docs = append(docs, manifest) + } + return docs +} diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go new file mode 100644 index 0000000000..9da1ddb4a0 --- /dev/null +++ b/internal/operator-controller/applier/boxcutter_test.go @@ -0,0 +1,923 @@ +package applier_test + +import ( + "context" + "errors" + "fmt" + "io/fs" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + k8scheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/internal/operator-controller/labels" +) + +func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) { + g := &applier.SimpleRevisionGenerator{} + + helmRelease := &release.Release{ + Name: "test-123", + Manifest: `{"apiVersion":"v1","kind":"ConfigMap"}` + "\n" + `{"apiVersion":"v1","kind":"Secret"}` + "\n", + Labels: map[string]string{ + labels.BundleNameKey: "my-bundle", + labels.PackageNameKey: "my-package", + labels.BundleVersionKey: "1.2.0", + labels.BundleReferenceKey: "bundle-ref", + }, + } + + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-123", + }, + } + + objectLabels := map[string]string{ + "my-label": "my-value", + } + + rev, err := g.GenerateRevisionFromHelmRelease(helmRelease, ext, objectLabels) + require.NoError(t, err) + + assert.Equal(t, &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-123-1", + Annotations: map[string]string{ + "olm.operatorframework.io/bundle-name": "my-bundle", + "olm.operatorframework.io/bundle-reference": "bundle-ref", + "olm.operatorframework.io/bundle-version": "1.2.0", + "olm.operatorframework.io/package-name": "my-package", + }, + Labels: map[string]string{ + "olm.operatorframework.io/owner": "test-123", + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Revision: 1, + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: "deploy", + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "my-label": "my-value", + }, + }, + }, + }, + CollisionProtection: ocv1.CollisionProtectionNone, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "my-label": "my-value", + }, + }, + }, + }, + CollisionProtection: ocv1.CollisionProtectionNone, + }, + }, + }, + }, + }, + }, rev) +} + +func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { + r := &FakeManifestProvider{ + GetFn: func(_ fs.FS, _ *ocv1.ClusterExtension) ([]client.Object, error) { + return []client.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-deployment", + }, + }, + }, nil + }, + } + + b := applier.SimpleRevisionGenerator{ + Scheme: k8scheme.Scheme, + ManifestProvider: r, + } + + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-extension", + }, + } + + rev, err := b.GenerateRevision(fstest.MapFS{}, ext, map[string]string{}, map[string]string{}) + require.NoError(t, err) + + t.Log("by checking the olm.operatorframework.io/owner label is set to the name of the ClusterExtension") + require.Equal(t, map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: "test-extension", + }, rev.Labels) + t.Log("by checking there are no annotations") + require.Empty(t, rev.Annotations) + t.Log("by checking the revision number is 0") + require.Equal(t, int64(0), rev.Spec.Revision) + t.Log("by checking the rendered objects are present in the correct phases") + require.Equal(t, []ocv1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Service", + "metadata": map[string]interface{}{ + "name": "test-service", + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{ + "loadBalancer": map[string]interface{}{}, + }, + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-deployment", + }, + "spec": map[string]interface{}{ + "selector": nil, + "template": map[string]interface{}{ + "metadata": map[string]interface{}{}, + "spec": map[string]interface{}{ + "containers": nil, + }, + }, + "strategy": map[string]interface{}{}, + }, + "status": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, rev.Spec.Phases) +} + +func Test_SimpleRevisionGenerator_Renderer_Integration(t *testing.T) { + bundleFS := fstest.MapFS{} + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-extension", + }, + } + r := &FakeManifestProvider{ + GetFn: func(b fs.FS, e *ocv1.ClusterExtension) ([]client.Object, error) { + t.Log("by checking renderer was called with the correct parameters") + require.Equal(t, bundleFS, b) + require.Equal(t, ext, e) + return nil, nil + }, + } + b := applier.SimpleRevisionGenerator{ + Scheme: k8scheme.Scheme, + ManifestProvider: r, + } + + _, err := b.GenerateRevision(bundleFS, ext, map[string]string{}, map[string]string{}) + require.NoError(t, err) +} + +func Test_SimpleRevisionGenerator_AppliesObjectLabelsAndRevisionAnnotations(t *testing.T) { + renderedObjs := []client.Object{ + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Labels: map[string]string{ + "app": "test-obj", + }, + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Labels: map[string]string{ + "app": "test-obj", + }, + }, + }, + } + r := &FakeManifestProvider{ + GetFn: func(b fs.FS, e *ocv1.ClusterExtension) ([]client.Object, error) { + return renderedObjs, nil + }, + } + + b := applier.SimpleRevisionGenerator{ + Scheme: k8scheme.Scheme, + ManifestProvider: r, + } + + revAnnotations := map[string]string{ + "other": "value", + } + + rev, err := b.GenerateRevision(fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{ + "some": "value", + }, revAnnotations) + require.NoError(t, err) + t.Log("by checking the rendered objects contain the given object labels") + for _, phase := range rev.Spec.Phases { + for _, revObj := range phase.Objects { + require.Equal(t, map[string]string{ + "app": "test-obj", + "some": "value", + }, revObj.Object.GetLabels()) + } + } + t.Log("by checking the generated revision contain the given annotations") + require.Equal(t, revAnnotations, rev.Annotations) +} + +func Test_SimpleRevisionGenerator_Failure(t *testing.T) { + r := &FakeManifestProvider{ + GetFn: func(b fs.FS, e *ocv1.ClusterExtension) ([]client.Object, error) { + return nil, fmt.Errorf("some-error") + }, + } + b := applier.SimpleRevisionGenerator{ + Scheme: k8scheme.Scheme, + ManifestProvider: r, + } + + rev, err := b.GenerateRevision(fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{}, map[string]string{}) + require.Nil(t, rev) + t.Log("by checking rendering errors are propagated") + require.Error(t, err) + require.Contains(t, err.Error(), "some-error") +} + +func TestBoxcutter_Apply(t *testing.T) { + testScheme := runtime.NewScheme() + require.NoError(t, ocv1.AddToScheme(testScheme)) + + // This is the revision that the mock builder will produce by default. + // We calculate its hash to use in the tests. + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ext", + UID: "test-uid", + }, + } + defaultDesiredRevision := &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ext-1", + UID: "rev-uid-1", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Revision: 1, + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "test-cm", + }, + }, + }, + }, + }, + }, + }, + }, + } + + allowedRevisionValue := func(revNum int64) *interceptor.Funcs { + return &interceptor.Funcs{ + Patch: func(ctx context.Context, client client.WithWatch, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + cer, ok := obj.(*ocv1.ClusterExtensionRevision) + if !ok { + return fmt.Errorf("expected ClusterExtensionRevision, got %T", obj) + } + fmt.Println(cer.Spec.Revision) + if cer.Spec.Revision != revNum { + fmt.Println("AAA") + return apierrors.NewInvalid(cer.GroupVersionKind().GroupKind(), cer.GetName(), field.ErrorList{field.Invalid(field.NewPath("spec.phases"), "immutable", "spec.phases is immutable")}) + } + return client.Patch(ctx, obj, patch, opts...) + }, + } + } + testCases := []struct { + name string + mockBuilder applier.ClusterExtensionRevisionGenerator + existingObjs []client.Object + expectedErr string + validate func(t *testing.T, c client.Client) + clientIterceptor *interceptor.Funcs + }{ + { + name: "first revision", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: revisionAnnotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "test-cm", + }, + }, + }, + }, + }, + }, + }, + }, + }, nil + }, + }, + validate: func(t *testing.T, c client.Client) { + revList := &ocv1.ClusterExtensionRevisionList{} + err := c.List(t.Context(), revList, client.MatchingLabels{controllers.ClusterExtensionRevisionOwnerLabel: ext.Name}) + require.NoError(t, err) + require.Len(t, revList.Items, 1) + + rev := revList.Items[0] + assert.Equal(t, "test-ext-1", rev.Name) + assert.Equal(t, int64(1), rev.Spec.Revision) + assert.Len(t, rev.OwnerReferences, 1) + assert.Equal(t, ext.Name, rev.OwnerReferences[0].Name) + assert.Equal(t, ext.UID, rev.OwnerReferences[0].UID) + }, + }, + { + name: "no change, revision exists", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: revisionAnnotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": "test-cm", + }, + }, + }, + }, + }, + }, + }, + }, + }, nil + }, + }, + existingObjs: []client.Object{ + defaultDesiredRevision, + }, + validate: func(t *testing.T, c client.Client) { + revList := &ocv1.ClusterExtensionRevisionList{} + err := c.List(context.Background(), revList, client.MatchingLabels{controllers.ClusterExtensionRevisionOwnerLabel: ext.Name}) + require.NoError(t, err) + // No new revision should be created + require.Len(t, revList.Items, 1) + assert.Equal(t, "test-ext-1", revList.Items[0].Name) + }, + }, + { + name: "new revision created when objects in new revision are different", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: revisionAnnotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": "new-secret", + }, + }, + }, + }, + }, + }, + }, + }, + }, nil + }, + }, + clientIterceptor: allowedRevisionValue(2), + existingObjs: []client.Object{ + defaultDesiredRevision, + }, + validate: func(t *testing.T, c client.Client) { + revList := &ocv1.ClusterExtensionRevisionList{} + err := c.List(context.Background(), revList, client.MatchingLabels{controllers.ClusterExtensionRevisionOwnerLabel: ext.Name}) + require.NoError(t, err) + require.Len(t, revList.Items, 2) + + // Find the new revision (rev 2) + var newRev ocv1.ClusterExtensionRevision + for _, r := range revList.Items { + if r.Spec.Revision == 2 { + newRev = r + break + } + } + require.NotNil(t, newRev) + + assert.Equal(t, "test-ext-2", newRev.Name) + assert.Equal(t, int64(2), newRev.Spec.Revision) + require.Len(t, newRev.Spec.Previous, 1) + assert.Equal(t, "test-ext-1", newRev.Spec.Previous[0].Name) + assert.Equal(t, types.UID("rev-uid-1"), newRev.Spec.Previous[0].UID) + }, + }, + { + name: "error from GenerateRevision", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return nil, errors.New("render boom") + }, + }, + expectedErr: "render boom", + validate: func(t *testing.T, c client.Client) { + // Ensure no revisions were created + revList := &ocv1.ClusterExtensionRevisionList{} + err := c.List(context.Background(), revList, client.MatchingLabels{controllers.ClusterExtensionRevisionOwnerLabel: ext.Name}) + require.NoError(t, err) + assert.Empty(t, revList.Items) + }, + }, + { + name: "keep at most 5 past revisions", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: revisionAnnotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{}, + }, nil + }, + }, + existingObjs: []client.Object{ + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-1", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 1, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-2", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 2, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-3", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 3, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-4", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 4, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-5", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 5, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-6", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 6, + }, + }, + }, + clientIterceptor: allowedRevisionValue(7), + validate: func(t *testing.T, c client.Client) { + rev1 := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{Name: "rev-1"}, rev1) + require.Error(t, err) + assert.True(t, apierrors.IsNotFound(err)) + + latest := &ocv1.ClusterExtensionRevision{} + err = c.Get(t.Context(), client.ObjectKey{Name: "test-ext-7"}, latest) + require.NoError(t, err) + assert.Len(t, latest.Spec.Previous, applier.ClusterExtensionRevisionPreviousLimit) + }, + }, + { + name: "keep active revisions when they are out of limit", + mockBuilder: &mockBundleRevisionBuilder{ + makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: revisionAnnotations, + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{}, + }, nil + }, + }, + existingObjs: []client.Object{ + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-1", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 1, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-2", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + // index beyond the retention limit but active; should be preserved + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateActive, + Revision: 2, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-3", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateActive, + Revision: 3, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-4", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + // archived but should be preserved since it is within the limit + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateArchived, + Revision: 4, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-5", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateActive, + Revision: 5, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-6", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateActive, + Revision: 6, + }, + }, + &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rev-7", + Labels: map[string]string{ + controllers.ClusterExtensionRevisionOwnerLabel: ext.Name, + }, + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + LifecycleState: ocv1.ClusterExtensionRevisionLifecycleStateActive, + Revision: 7, + }, + }, + }, + clientIterceptor: allowedRevisionValue(8), + validate: func(t *testing.T, c client.Client) { + rev1 := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{Name: "rev-1"}, rev1) + require.Error(t, err) + assert.True(t, apierrors.IsNotFound(err)) + + rev2 := &ocv1.ClusterExtensionRevision{} + err = c.Get(t.Context(), client.ObjectKey{Name: "rev-2"}, rev2) + require.NoError(t, err) + + rev4 := &ocv1.ClusterExtensionRevision{} + err = c.Get(t.Context(), client.ObjectKey{Name: "rev-4"}, rev4) + require.NoError(t, err) + + latest := &ocv1.ClusterExtensionRevision{} + err = c.Get(t.Context(), client.ObjectKey{Name: "test-ext-8"}, latest) + require.NoError(t, err) + assert.Len(t, latest.Spec.Previous, 6) + assert.Contains(t, latest.Spec.Previous, ocv1.ClusterExtensionRevisionPrevious{Name: "rev-4"}) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + cb := fake.NewClientBuilder().WithScheme(testScheme).WithObjects(tc.existingObjs...) + if tc.clientIterceptor != nil { + cb.WithInterceptorFuncs(*tc.clientIterceptor) + } + fakeClient := cb.Build() + + boxcutter := &applier.Boxcutter{ + Client: fakeClient, + Scheme: testScheme, + RevisionGenerator: tc.mockBuilder, + FieldOwner: "test-owner", + } + + // We need a dummy fs.FS + testFS := fstest.MapFS{} + + // Execute + installSucceeded, installStatus, err := boxcutter.Apply(t.Context(), testFS, ext, nil, nil) + + // Assert + if tc.expectedErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErr) + assert.False(t, installSucceeded) + assert.Empty(t, installStatus) + } else { + require.NoError(t, err) + assert.False(t, installSucceeded) + assert.Equal(t, "New revision created", installStatus) + } + + if tc.validate != nil { + // For the client create error, we need a client that *will* error. + // Since we can't do that easily, we will skip validation for that specific path + // as the state won't be what we expect. + if tc.name != "error from client create" { + tc.validate(t, fakeClient) + } + } + }) + } +} + +func TestBoxcutterStorageMigrator(t *testing.T) { + t.Run("creates revision", func(t *testing.T) { + brb := &mockBundleRevisionBuilder{} + mag := &mockActionGetter{} + client := &clientMock{} + sm := &applier.BoxcutterStorageMigrator{ + RevisionGenerator: brb, + ActionClientGetter: mag, + Client: client, + } + + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: "test123"}, + } + + client. + On("List", mock.Anything, mock.AnythingOfType("*v1.ClusterExtensionRevisionList"), mock.Anything). + Return(nil) + client. + On("Create", mock.Anything, mock.AnythingOfType("*v1.ClusterExtensionRevision"), mock.Anything). + Once(). + Return(nil) + + err := sm.Migrate(t.Context(), ext, map[string]string{"my-label": "my-value"}) + require.NoError(t, err) + + client.AssertExpectations(t) + }) + + t.Run("does not create revision when revisions exist", func(t *testing.T) { + brb := &mockBundleRevisionBuilder{} + mag := &mockActionGetter{} + client := &clientMock{} + sm := &applier.BoxcutterStorageMigrator{ + RevisionGenerator: brb, + ActionClientGetter: mag, + Client: client, + } + + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: "test123"}, + } + + client. + On("List", mock.Anything, mock.AnythingOfType("*v1.ClusterExtensionRevisionList"), mock.Anything). + Run(func(args mock.Arguments) { + list := args.Get(1).(*ocv1.ClusterExtensionRevisionList) + list.Items = []ocv1.ClusterExtensionRevision{ + {}, {}, // Existing revisions. + } + }). + Return(nil) + + err := sm.Migrate(t.Context(), ext, map[string]string{"my-label": "my-value"}) + require.NoError(t, err) + + client.AssertExpectations(t) + }) + + t.Run("does not create revision when no helm release", func(t *testing.T) { + brb := &mockBundleRevisionBuilder{} + mag := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + } + client := &clientMock{} + sm := &applier.BoxcutterStorageMigrator{ + RevisionGenerator: brb, + ActionClientGetter: mag, + Client: client, + } + + ext := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: "test123"}, + } + + client. + On("List", mock.Anything, mock.AnythingOfType("*v1.ClusterExtensionRevisionList"), mock.Anything). + Return(nil) + + err := sm.Migrate(t.Context(), ext, map[string]string{"my-label": "my-value"}) + require.NoError(t, err) + + client.AssertExpectations(t) + }) +} + +// mockBundleRevisionBuilder is a mock implementation of the ClusterExtensionRevisionGenerator for testing. +type mockBundleRevisionBuilder struct { + makeRevisionFunc func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotation map[string]string) (*ocv1.ClusterExtensionRevision, error) +} + +func (m *mockBundleRevisionBuilder) GenerateRevision(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return m.makeRevisionFunc(bundleFS, ext, objectLabels, revisionAnnotations) +} + +func (m *mockBundleRevisionBuilder) GenerateRevisionFromHelmRelease( + helmRelease *release.Release, ext *ocv1.ClusterExtension, + objectLabels map[string]string, +) (*ocv1.ClusterExtensionRevision, error) { + return nil, nil +} + +type clientMock struct { + mock.Mock +} + +func (m *clientMock) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + args := m.Called(ctx, list, opts) + return args.Error(0) +} + +func (m *clientMock) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go new file mode 100644 index 0000000000..4e70268941 --- /dev/null +++ b/internal/operator-controller/applier/helm.go @@ -0,0 +1,330 @@ +package applier + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "slices" + "strings" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/postrender" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" + "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager" + "github.com/operator-framework/operator-controller/internal/operator-controller/features" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" +) + +// HelmChartProvider provides helm charts from bundle sources and cluster extensions +type HelmChartProvider interface { + Get(bundle fs.FS, clusterExtension *ocv1.ClusterExtension) (*chart.Chart, error) +} + +type HelmReleaseToObjectsConverter struct { +} + +type HelmReleaseToObjectsConverterInterface interface { + GetObjectsFromRelease(rel *release.Release) ([]client.Object, error) +} + +func (h HelmReleaseToObjectsConverter) GetObjectsFromRelease(rel *release.Release) ([]client.Object, error) { + if rel == nil { + return nil, nil + } + + relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) + if err != nil { + return nil, fmt.Errorf("parsing release %q objects: %w", rel.Name, err) + } + return relObjects, nil +} + +type Helm struct { + ActionClientGetter helmclient.ActionClientGetter + Preflights []Preflight + PreAuthorizer authorization.PreAuthorizer + HelmChartProvider HelmChartProvider + HelmReleaseToObjectsConverter HelmReleaseToObjectsConverterInterface + + Manager contentmanager.Manager + Watcher crcontroller.Controller +} + +// runPreAuthorizationChecks performs pre-authorization checks for a Helm release +// it renders a client-only release, checks permissions using the PreAuthorizer +// and returns an error if authorization fails or required permissions are missing +func (h *Helm) runPreAuthorizationChecks(ctx context.Context, ext *ocv1.ClusterExtension, chart *chart.Chart, values chartutil.Values, post postrender.PostRenderer) error { + tmplRel, err := h.renderClientOnlyRelease(ctx, ext, chart, values, post) + if err != nil { + return fmt.Errorf("error rendering content for pre-authorization checks: %w", err) + } + + missingRules, authErr := h.PreAuthorizer.PreAuthorize(ctx, ext, strings.NewReader(tmplRel.Manifest)) + + var preAuthErrors []error + + if len(missingRules) > 0 { + var missingRuleDescriptions []string + for _, policyRules := range missingRules { + for _, rule := range policyRules.MissingRules { + missingRuleDescriptions = append(missingRuleDescriptions, ruleDescription(policyRules.Namespace, rule)) + } + } + slices.Sort(missingRuleDescriptions) + // This phrase is explicitly checked by external testing + preAuthErrors = append(preAuthErrors, fmt.Errorf("service account requires the following permissions to manage cluster extension:\n %s", strings.Join(missingRuleDescriptions, "\n "))) + } + if authErr != nil { + preAuthErrors = append(preAuthErrors, fmt.Errorf("authorization evaluation error: %w", authErr)) + } + if len(preAuthErrors) > 0 { + // This phrase is explicitly checked by external testing + return fmt.Errorf("pre-authorization failed: %v", errors.Join(preAuthErrors...)) + } + return nil +} + +func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) (bool, string, error) { + chrt, err := h.buildHelmChart(contentFS, ext) + if err != nil { + return false, "", err + } + values := chartutil.Values{} + + post := &postrenderer{ + labels: objectLabels, + } + + if h.PreAuthorizer != nil { + err := h.runPreAuthorizationChecks(ctx, ext, chrt, values, post) + if err != nil { + // Return the pre-authorization error directly + return false, "", err + } + } + + ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) + if err != nil { + return false, "", err + } + + rel, desiredRel, state, err := h.getReleaseState(ac, ext, chrt, values, post) + if err != nil { + return false, "", fmt.Errorf("failed to get release state using server-side dry-run: %w", err) + } + objs, err := h.HelmReleaseToObjectsConverter.GetObjectsFromRelease(desiredRel) + if err != nil { + return false, "", err + } + + for _, preflight := range h.Preflights { + if shouldSkipPreflight(ctx, preflight, ext, state) { + continue + } + switch state { + case StateNeedsInstall: + err := preflight.Install(ctx, objs) + if err != nil { + return false, "", err + } + case StateNeedsUpgrade: + err := preflight.Upgrade(ctx, objs) + if err != nil { + return false, "", err + } + } + } + + switch state { + case StateNeedsInstall: + rel, err = ac.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(install *action.Install) error { + install.CreateNamespace = false + install.Labels = storageLabels + return nil + }, helmclient.AppendInstallPostRenderer(post)) + if err != nil { + return false, "", err + } + case StateNeedsUpgrade: + rel, err = ac.Upgrade(ext.GetName(), ext.Spec.Namespace, chrt, values, func(upgrade *action.Upgrade) error { + upgrade.MaxHistory = maxHelmReleaseHistory + upgrade.Labels = storageLabels + return nil + }, helmclient.AppendUpgradePostRenderer(post)) + if err != nil { + return false, "", err + } + case StateUnchanged: + if err := ac.Reconcile(rel); err != nil { + return false, "", err + } + default: + return false, "", fmt.Errorf("unexpected release state %q", state) + } + + relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) + if err != nil { + return true, "", err + } + klog.FromContext(ctx).Info("watching managed objects") + cache, err := h.Manager.Get(ctx, ext) + if err != nil { + return true, "", err + } + + if err := cache.Watch(ctx, h.Watcher, relObjects...); err != nil { + return true, "", err + } + + return true, "", nil +} + +func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + if h.HelmChartProvider == nil { + return nil, errors.New("HelmChartProvider is nil") + } + if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) { + meta := new(chart.Metadata) + if ok, _ := imageutil.IsBundleSourceChart(bundleFS, meta); ok { + return imageutil.LoadChartFSWithOptions( + bundleFS, + fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version), + imageutil.WithInstallNamespace(ext.Spec.Namespace), + ) + } + } + return h.HelmChartProvider.Get(bundleFS, ext) +} + +func (h *Helm) renderClientOnlyRelease(ctx context.Context, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, error) { + // We need to get a separate action client because our work below + // permanently modifies the underlying action.Configuration for ClientOnly mode. + ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext) + if err != nil { + return nil, err + } + + isUpgrade := false + currentRelease, err := ac.Get(ext.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, err + } + if currentRelease != nil { + isUpgrade = true + } + + return ac.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(i *action.Install) error { + i.DryRun = true + i.ReleaseName = ext.GetName() + i.Replace = true + i.ClientOnly = true + i.IncludeCRDs = true + i.IsUpgrade = isUpgrade + return nil + }, helmclient.AppendInstallPostRenderer(post)) +} + +func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, *release.Release, string, error) { + currentRelease, err := cl.Get(ext.GetName()) + if errors.Is(err, driver.ErrReleaseNotFound) { + desiredRelease, err := cl.Install(ext.GetName(), ext.Spec.Namespace, chrt, values, func(i *action.Install) error { + i.DryRun = true + i.DryRunOption = "server" + return nil + }, helmclient.AppendInstallPostRenderer(post)) + if err != nil { + return nil, nil, StateError, err + } + return nil, desiredRelease, StateNeedsInstall, nil + } + if err != nil { + return nil, nil, StateError, err + } + + desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.Namespace, chrt, values, func(upgrade *action.Upgrade) error { + upgrade.MaxHistory = maxHelmReleaseHistory + upgrade.DryRun = true + upgrade.DryRunOption = "server" + return nil + }, helmclient.AppendUpgradePostRenderer(post)) + if err != nil { + return currentRelease, nil, StateError, err + } + relState := StateUnchanged + if desiredRelease.Manifest != currentRelease.Manifest || + currentRelease.Info.Status == release.StatusFailed || + currentRelease.Info.Status == release.StatusSuperseded { + relState = StateNeedsUpgrade + } + return currentRelease, desiredRelease, relState, nil +} + +type postrenderer struct { + labels map[string]string + cascade postrender.PostRenderer +} + +func (p *postrenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { + var buf bytes.Buffer + dec := apimachyaml.NewYAMLOrJSONDecoder(renderedManifests, 1024) + for { + obj := unstructured.Unstructured{} + err := dec.Decode(&obj) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + obj.SetLabels(util.MergeMaps(obj.GetLabels(), p.labels)) + b, err := obj.MarshalJSON() + if err != nil { + return nil, err + } + buf.Write(b) + } + if p.cascade != nil { + return p.cascade.Run(&buf) + } + return &buf, nil +} + +func ruleDescription(ns string, rule rbacv1.PolicyRule) string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Namespace:%q", ns)) + + if len(rule.APIGroups) > 0 { + sb.WriteString(fmt.Sprintf(" APIGroups:[%s]", strings.Join(slices.Sorted(slices.Values(rule.APIGroups)), ","))) + } + if len(rule.Resources) > 0 { + sb.WriteString(fmt.Sprintf(" Resources:[%s]", strings.Join(slices.Sorted(slices.Values(rule.Resources)), ","))) + } + if len(rule.ResourceNames) > 0 { + sb.WriteString(fmt.Sprintf(" ResourceNames:[%s]", strings.Join(slices.Sorted(slices.Values(rule.ResourceNames)), ","))) + } + if len(rule.Verbs) > 0 { + sb.WriteString(fmt.Sprintf(" Verbs:[%s]", strings.Join(slices.Sorted(slices.Values(rule.Verbs)), ","))) + } + if len(rule.NonResourceURLs) > 0 { + sb.WriteString(fmt.Sprintf(" NonResourceURLs:[%s]", strings.Join(slices.Sorted(slices.Values(rule.NonResourceURLs)), ","))) + } + return sb.String() +} diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go new file mode 100644 index 0000000000..8d07de9123 --- /dev/null +++ b/internal/operator-controller/applier/helm_test.go @@ -0,0 +1,660 @@ +package applier_test + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" + "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager" + cmcache "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache" +) + +var _ contentmanager.Manager = (*mockManagedContentCacheManager)(nil) + +type mockManagedContentCacheManager struct { + err error + cache cmcache.Cache +} + +func (m *mockManagedContentCacheManager) Get(_ context.Context, _ *ocv1.ClusterExtension) (cmcache.Cache, error) { + if m.err != nil { + return nil, m.err + } + return m.cache, nil +} + +func (m *mockManagedContentCacheManager) Delete(_ *ocv1.ClusterExtension) error { + return m.err +} + +type mockManagedContentCache struct { + err error +} + +var _ cmcache.Cache = (*mockManagedContentCache)(nil) + +func (m *mockManagedContentCache) Close() error { + if m.err != nil { + return m.err + } + return nil +} + +func (m *mockManagedContentCache) Watch(_ context.Context, _ cmcache.Watcher, _ ...client.Object) error { + if m.err != nil { + return m.err + } + return nil +} + +type mockPreflight struct { + installErr error + upgradeErr error +} + +type mockPreAuthorizer struct { + missingRules []authorization.ScopedPolicyRules + returnError error +} + +func (p *mockPreAuthorizer) PreAuthorize( + ctx context.Context, + ext *ocv1.ClusterExtension, + manifestReader io.Reader, +) ([]authorization.ScopedPolicyRules, error) { + return p.missingRules, p.returnError +} + +func (mp *mockPreflight) Install(context.Context, []client.Object) error { + return mp.installErr +} + +func (mp *mockPreflight) Upgrade(context.Context, []client.Object) error { + return mp.upgradeErr +} + +type mockHelmReleaseToObjectsConverter struct { +} + +func (mockHelmReleaseToObjectsConverter) GetObjectsFromRelease(*release.Release) ([]client.Object, error) { + return nil, nil +} + +type mockActionGetter struct { + actionClientForErr error + getClientErr error + installErr error + dryRunInstallErr error + upgradeErr error + dryRunUpgradeErr error + reconcileErr error + desiredRel *release.Release + currentRel *release.Release +} + +func (mag *mockActionGetter) ActionClientFor(ctx context.Context, obj client.Object) (helmclient.ActionInterface, error) { + return mag, mag.actionClientForErr +} + +func (mag *mockActionGetter) Get(name string, opts ...helmclient.GetOption) (*release.Release, error) { + return mag.currentRel, mag.getClientErr +} + +func (mag *mockActionGetter) History(name string, opts ...helmclient.HistoryOption) ([]*release.Release, error) { + return nil, mag.getClientErr +} + +func (mag *mockActionGetter) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.InstallOption) (*release.Release, error) { + i := action.Install{} + for _, opt := range opts { + if err := opt(&i); err != nil { + return nil, err + } + } + if i.DryRun { + return mag.desiredRel, mag.dryRunInstallErr + } + return mag.desiredRel, mag.installErr +} + +func (mag *mockActionGetter) Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.UpgradeOption) (*release.Release, error) { + i := action.Upgrade{} + for _, opt := range opts { + if err := opt(&i); err != nil { + return nil, err + } + } + if i.DryRun { + return mag.desiredRel, mag.dryRunUpgradeErr + } + return mag.desiredRel, mag.upgradeErr +} + +func (mag *mockActionGetter) Uninstall(name string, opts ...helmclient.UninstallOption) (*release.UninstallReleaseResponse, error) { + return nil, nil +} + +func (mag *mockActionGetter) Reconcile(rel *release.Release) error { + return mag.reconcileErr +} + +var ( + // required for unmockable call to convert.RegistryV1ToHelmChart + validFS = fstest.MapFS{ + "metadata/annotations.yaml": &fstest.MapFile{Data: []byte(`annotations: + operators.operatorframework.io.bundle.package.v1: my-package`)}, + "manifests": &fstest.MapFile{Data: []byte(`apiVersion: operators.operatorframework.io/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: test.v1.0.0 + annotations: + olm.properties: '[{"type":"from-csv-annotations-key", "value":"from-csv-annotations-value"}]' +spec: + installModes: + - type: SingleNamespace + supported: true + - type: OwnNamespace + supported: true + - type: AllNamespaces + supported: true`)}, + } + + // required for unmockable call to util.ManifestObjects + validManifest = `apiVersion: v1 +kind: Service +metadata: + name: service-a + namespace: ns-a +spec: + clusterIP: None +--- +apiVersion: v1 +kind: Service +metadata: + name: service-b + namespace: ns-b +spec: + clusterIP: 0.0.0.0` + + testCE = &ocv1.ClusterExtension{} + testObjectLabels = map[string]string{"object": "label"} + testStorageLabels = map[string]string{"storage": "label"} + errPreAuth = errors.New("problem running preauthorization") + missingRBAC = []authorization.ScopedPolicyRules{ + { + Namespace: "", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + }, + }, + { + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + }, + }, + } + + errMissingRBAC = `pre-authorization failed: service account requires the following permissions to manage cluster extension: + Namespace:"" APIGroups:[] Resources:[services] Verbs:[list,watch] + Namespace:"test-namespace" APIGroups:[*] Resources:[certificates] Verbs:[create]` +) + +func TestApply_Base(t *testing.T) { + t.Run("fails converting content FS to helm chart", func(t *testing.T) { + helmApplier := applier.Helm{} + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), os.DirFS("/"), testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails trying to obtain an action client", func(t *testing.T) { + mockAcg := &mockActionGetter{actionClientForErr: errors.New("failed getting action client")} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "getting action client") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails getting current release and !driver.ErrReleaseNotFound", func(t *testing.T) { + mockAcg := &mockActionGetter{getClientErr: errors.New("failed getting current release")} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "getting current release") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) +} + +func TestApply_Installation(t *testing.T) { + t.Run("fails during dry-run installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + dryRunInstallErr: errors.New("failed attempting to dry-run install chart"), + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "attempting to dry-run install chart") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during pre-flight installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + installErr: errors.New("failed installing chart"), + } + mockPf := &mockPreflight{installErr: errors.New("failed during install pre-flight check")} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + Preflights: []applier.Preflight{mockPf}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "install pre-flight check") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + installErr: errors.New("failed installing chart"), + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "installing chart") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("successful installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + Manager: &mockManagedContentCacheManager{ + cache: &mockManagedContentCache{}, + }, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.NoError(t, err) + require.Empty(t, installStatus) + require.True(t, installSucceeded) + }) +} + +func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { + t.Run("fails during dry-run installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + dryRunInstallErr: errors.New("failed attempting to dry-run install chart"), + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "attempting to dry-run install chart") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during pre-flight installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + installErr: errors.New("failed installing chart"), + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + mockPf := &mockPreflight{installErr: errors.New("failed during install pre-flight check")} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + Preflights: []applier.Preflight{mockPf}, + PreAuthorizer: &mockPreAuthorizer{nil, nil}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "install pre-flight check") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during installation because of pre-authorization failure", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + PreAuthorizer: &mockPreAuthorizer{nil, errPreAuth}, + HelmChartProvider: DummyHelmChartProvider, + } + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "problem running preauthorization") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during installation due to missing RBAC rules", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + PreAuthorizer: &mockPreAuthorizer{missingRBAC, nil}, + HelmChartProvider: DummyHelmChartProvider, + } + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, errMissingRBAC) + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("successful installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + PreAuthorizer: &mockPreAuthorizer{nil, nil}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + Manager: &mockManagedContentCacheManager{ + cache: &mockManagedContentCache{}, + }, + } + + // Use a ClusterExtension with valid Spec fields. + validCE := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels) + require.NoError(t, err) + require.Empty(t, installStatus) + require.True(t, installSucceeded) + }) +} + +func TestApply_Upgrade(t *testing.T) { + testCurrentRelease := &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + } + + t.Run("fails during dry-run upgrade", func(t *testing.T) { + mockAcg := &mockActionGetter{ + dryRunUpgradeErr: errors.New("failed attempting to dry-run upgrade chart"), + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "attempting to dry-run upgrade chart") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during pre-flight upgrade", func(t *testing.T) { + testDesiredRelease := *testCurrentRelease + testDesiredRelease.Manifest = "do-not-match-current" + + mockAcg := &mockActionGetter{ + upgradeErr: errors.New("failed upgrading chart"), + currentRel: testCurrentRelease, + desiredRel: &testDesiredRelease, + } + mockPf := &mockPreflight{upgradeErr: errors.New("failed during upgrade pre-flight check")} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + Preflights: []applier.Preflight{mockPf}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "upgrade pre-flight check") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during upgrade", func(t *testing.T) { + testDesiredRelease := *testCurrentRelease + testDesiredRelease.Manifest = "do-not-match-current" + + mockAcg := &mockActionGetter{ + upgradeErr: errors.New("failed upgrading chart"), + currentRel: testCurrentRelease, + desiredRel: &testDesiredRelease, + } + mockPf := &mockPreflight{} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, Preflights: []applier.Preflight{mockPf}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "upgrading chart") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("fails during upgrade reconcile (StateUnchanged)", func(t *testing.T) { + // make sure desired and current are the same this time + testDesiredRelease := *testCurrentRelease + + mockAcg := &mockActionGetter{ + reconcileErr: errors.New("failed reconciling charts"), + currentRel: testCurrentRelease, + desiredRel: &testDesiredRelease, + } + mockPf := &mockPreflight{} + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + Preflights: []applier.Preflight{mockPf}, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "reconciling charts") + require.False(t, installSucceeded) + require.Empty(t, installStatus) + }) + + t.Run("successful upgrade", func(t *testing.T) { + testDesiredRelease := *testCurrentRelease + testDesiredRelease.Manifest = validManifest + + mockAcg := &mockActionGetter{ + currentRel: testCurrentRelease, + desiredRel: &testDesiredRelease, + } + helmApplier := applier.Helm{ + ActionClientGetter: mockAcg, + HelmChartProvider: DummyHelmChartProvider, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + Manager: &mockManagedContentCacheManager{ + cache: &mockManagedContentCache{}, + }, + } + + installSucceeded, installStatus, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.NoError(t, err) + require.True(t, installSucceeded) + require.Empty(t, installStatus) + }) +} + +func TestApply_RegistryV1ToChartConverterIntegration(t *testing.T) { + t.Run("generates bundle resources in AllNamespaces install mode", func(t *testing.T) { + helmApplier := applier.Helm{ + ActionClientGetter: &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + }, + HelmChartProvider: &FakeHelmChartProvider{ + fn: func(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + require.Equal(t, testCE, ext) + return nil, nil + }, + }, + HelmReleaseToObjectsConverter: mockHelmReleaseToObjectsConverter{}, + Manager: &mockManagedContentCacheManager{ + cache: &mockManagedContentCache{}, + }, + } + + _, _, _ = helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + }) + + t.Run("surfaces chart generation errors", func(t *testing.T) { + helmApplier := applier.Helm{ + ActionClientGetter: &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + desiredRel: &release.Release{ + Info: &release.Info{Status: release.StatusDeployed}, + Manifest: validManifest, + }, + }, + HelmChartProvider: &FakeHelmChartProvider{ + fn: func(bundleFs fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + return nil, errors.New("some error") + }, + }, + Manager: &mockManagedContentCacheManager{ + cache: &mockManagedContentCache{}, + }, + } + + _, _, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.ErrorContains(t, err, "some error") + }) +} + +type FakeHelmChartProvider struct { + fn func(fs.FS, *ocv1.ClusterExtension) (*chart.Chart, error) +} + +func (f FakeHelmChartProvider) Get(bundle fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + return f.fn(bundle, ext) +} + +var DummyHelmChartProvider = &FakeHelmChartProvider{ + fn: func(fs fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + return &chart.Chart{}, nil + }, +} diff --git a/internal/operator-controller/applier/phase.go b/internal/operator-controller/applier/phase.go new file mode 100644 index 0000000000..9ae31db6a7 --- /dev/null +++ b/internal/operator-controller/applier/phase.go @@ -0,0 +1,136 @@ +package applier + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// The following, with modifications, is taken from: +// https://github.com/package-operator/package-operator/blob/v1.18.2/internal/packages/internal/packagekickstart/presets/phases.go +// +// Determines a phase using the objects Group Kind from a list of presets. +// Defaults to the `deploy` phase if no preset was found. Runtimes that +// depend on a custom resource to start i.e. certmanager's Certificate +// will require this. +func determinePhase(gk schema.GroupKind) Phase { + phase, ok := gkPhaseMap[gk] + if !ok { + return PhaseDeploy + } + return phase +} + +// Phase represents a well-known phase. +type Phase string + +const ( + PhaseNamespaces Phase = "namespaces" + PhasePolicies Phase = "policies" + PhaseRBAC Phase = "rbac" + PhaseCRDs Phase = "crds" + PhaseStorage Phase = "storage" + PhaseDeploy Phase = "deploy" + PhasePublish Phase = "publish" +) + +// Well known phases ordered. +var defaultPhaseOrder = []Phase{ + PhaseNamespaces, + PhasePolicies, + PhaseRBAC, + PhaseCRDs, + PhaseStorage, + PhaseDeploy, + PhasePublish, +} + +var ( + // This will be populated from `phaseGKMap` in an init func! + gkPhaseMap = map[schema.GroupKind]Phase{} + phaseGKMap = map[Phase][]schema.GroupKind{ + PhaseNamespaces: { + {Kind: "Namespace"}, + }, + + PhasePolicies: { + {Kind: "ResourceQuota"}, + {Kind: "LimitRange"}, + {Kind: "PriorityClass", Group: "scheduling.k8s.io"}, + {Kind: "NetworkPolicy", Group: "networking.k8s.io"}, + {Kind: "HorizontalPodAutoscaler", Group: "autoscaling"}, + {Kind: "PodDisruptionBudget", Group: "policy"}, + }, + + PhaseRBAC: { + {Kind: "ServiceAccount"}, + {Kind: "Role", Group: "rbac.authorization.k8s.io"}, + {Kind: "RoleBinding", Group: "rbac.authorization.k8s.io"}, + {Kind: "ClusterRole", Group: "rbac.authorization.k8s.io"}, + {Kind: "ClusterRoleBinding", Group: "rbac.authorization.k8s.io"}, + }, + + PhaseCRDs: { + {Kind: "CustomResourceDefinition", Group: "apiextensions.k8s.io"}, + }, + + PhaseStorage: { + {Kind: "PersistentVolume"}, + {Kind: "PersistentVolumeClaim"}, + {Kind: "StorageClass", Group: "storage.k8s.io"}, + }, + + PhaseDeploy: { + {Kind: "Deployment", Group: "apps"}, + {Kind: "DaemonSet", Group: "apps"}, + {Kind: "StatefulSet", Group: "apps"}, + {Kind: "ReplicaSet"}, + {Kind: "Pod"}, // probing complicated, may be either Completed or Available. + {Kind: "Job", Group: "batch"}, + {Kind: "CronJob", Group: "batch"}, + {Kind: "Service"}, + {Kind: "Secret"}, + {Kind: "ConfigMap"}, + }, + + PhasePublish: { + {Kind: "Ingress", Group: "networking.k8s.io"}, + {Kind: "APIService", Group: "apiregistration.k8s.io"}, + {Kind: "Route", Group: "route.openshift.io"}, + {Kind: "MutatingWebhookConfiguration", Group: "admissionregistration.k8s.io"}, + {Kind: "ValidatingWebhookConfiguration", Group: "admissionregistration.k8s.io"}, + }, + } +) + +func init() { + for phase, gks := range phaseGKMap { + for _, gk := range gks { + gkPhaseMap[gk] = phase + } + } +} + +// PhaseSort takes an unsorted list of objects and organizes them into sorted phases. +// Each phase will be applied in order according to DefaultPhaseOrder. Objects +// within a single phase are applied simultaneously. +func PhaseSort(unsortedObjs []ocv1.ClusterExtensionRevisionObject) []ocv1.ClusterExtensionRevisionPhase { + phasesSorted := make([]ocv1.ClusterExtensionRevisionPhase, 0) + phaseMap := make(map[Phase][]ocv1.ClusterExtensionRevisionObject, 0) + + for _, obj := range unsortedObjs { + phase := determinePhase(obj.Object.GroupVersionKind().GroupKind()) + phaseMap[phase] = append(phaseMap[phase], obj) + } + + for _, phaseName := range defaultPhaseOrder { + if objs, ok := phaseMap[phaseName]; ok { + phasesSorted = append(phasesSorted, ocv1.ClusterExtensionRevisionPhase{ + Name: string(phaseName), + Objects: objs, + }) + } + } + + return phasesSorted +} diff --git a/internal/operator-controller/applier/phase_test.go b/internal/operator-controller/applier/phase_test.go new file mode 100644 index 0000000000..3f2d85d0b1 --- /dev/null +++ b/internal/operator-controller/applier/phase_test.go @@ -0,0 +1,292 @@ +package applier_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + v1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" +) + +func Test_PhaseSort(t *testing.T) { + for _, tt := range []struct { + name string + objs []v1.ClusterExtensionRevisionObject + want []v1.ClusterExtensionRevisionPhase + }{ + { + name: "single deploy obj", + objs: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + }, + want: []v1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseDeploy), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + }, + }, + }, + }, + { + name: "all phases", + objs: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiregistration.k8s.io/v1", + "kind": "APIService", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "some.api/v1", + "kind": "SomeCustomResource", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "PersistentVolume", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + }, + }, + }, + }, + want: []v1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseNamespaces), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + }, + }, + }, + }, + }, + { + Name: string(applier.PhasePolicies), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + }, + }, + }, + }, + }, + { + Name: string(applier.PhaseRBAC), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + }, + }, + }, + }, + }, + { + Name: string(applier.PhaseCRDs), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + }, + }, + }, + }, + }, + { + Name: string(applier.PhaseStorage), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "PersistentVolume", + }, + }, + }, + }, + }, + { + Name: string(applier.PhaseDeploy), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "some.api/v1", + "kind": "SomeCustomResource", + }, + }, + }, + }, + }, + { + Name: string(applier.PhasePublish), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apiregistration.k8s.io/v1", + "kind": "APIService", + }, + }, + }, + }, + }, + }, + }, + { + name: "sorted and batched", + objs: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ServiceAccount", + }, + }, + }, + }, + want: []v1.ClusterExtensionRevisionPhase{ + { + Name: string(applier.PhaseRBAC), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ServiceAccount", + }, + }, + }, + }, + }, + { + Name: string(applier.PhaseDeploy), + Objects: []v1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + }, + }, + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + }, + }, + }, + }, + }, + }, + }, + { + name: "no objects", + objs: []v1.ClusterExtensionRevisionObject{}, + want: []v1.ClusterExtensionRevisionPhase{}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, applier.PhaseSort(tt.objs)) + }) + } +} diff --git a/internal/operator-controller/applier/preflight.go b/internal/operator-controller/applier/preflight.go new file mode 100644 index 0000000000..7b5d91e279 --- /dev/null +++ b/internal/operator-controller/applier/preflight.go @@ -0,0 +1,54 @@ +package applier + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" +) + +const ( + StateNeedsInstall string = "NeedsInstall" + StateNeedsUpgrade string = "NeedsUpgrade" + StateUnchanged string = "Unchanged" + StateError string = "Error" + maxHelmReleaseHistory = 10 +) + +// Preflight is a check that should be run before making any changes to the cluster +type Preflight interface { + // Install runs checks that should be successful prior + // to installing the Helm release. It is provided + // a Helm release and returns an error if the + // check is unsuccessful + Install(context.Context, []client.Object) error + + // Upgrade runs checks that should be successful prior + // to upgrading the Helm release. It is provided + // a Helm release and returns an error if the + // check is unsuccessful + Upgrade(context.Context, []client.Object) error +} + +// shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND +// if it is set to enforcement None. +func shouldSkipPreflight(ctx context.Context, preflight Preflight, ext *ocv1.ClusterExtension, state string) bool { + l := log.FromContext(ctx) + hasCRDUpgradeSafety := ext.Spec.Install != nil && ext.Spec.Install.Preflight != nil && ext.Spec.Install.Preflight.CRDUpgradeSafety != nil + _, isCRDUpgradeSafetyInstance := preflight.(*crdupgradesafety.Preflight) + + if hasCRDUpgradeSafety && isCRDUpgradeSafetyInstance { + if state == StateNeedsInstall || state == StateNeedsUpgrade { + l.Info("crdUpgradeSafety ", "policy", ext.Spec.Install.Preflight.CRDUpgradeSafety.Enforcement) + } + if ext.Spec.Install.Preflight.CRDUpgradeSafety.Enforcement == ocv1.CRDUpgradeSafetyEnforcementNone { + // Skip this preflight check because it is of type *crdupgradesafety.Preflight and the CRD Upgrade Safety + // policy is set to None + return true + } + } + return false +} diff --git a/internal/operator-controller/applier/provider.go b/internal/operator-controller/applier/provider.go new file mode 100644 index 0000000000..ffb5eb559b --- /dev/null +++ b/internal/operator-controller/applier/provider.go @@ -0,0 +1,150 @@ +package applier + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io/fs" + + "helm.sh/helm/v3/pkg/chart" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" +) + +// ManifestProvider returns the manifests that should be applied by OLM given a bundle and its associated ClusterExtension +type ManifestProvider interface { + // Get returns a set of resource manifests in bundle that take into account the configuration in ext + Get(bundle fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) +} + +// RegistryV1ManifestProvider generates the manifests that should be installed for a registry+v1 bundle +// given the user specified configuration given by the ClusterExtension API surface +type RegistryV1ManifestProvider struct { + BundleRenderer render.BundleRenderer + CertificateProvider render.CertificateProvider + IsWebhookSupportEnabled bool + IsSingleOwnNamespaceEnabled bool +} + +func (r *RegistryV1ManifestProvider) Get(bundleFS fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) { + rv1, err := source.FromFS(bundleFS).GetBundle() + if err != nil { + return nil, err + } + + if len(rv1.CSV.Spec.APIServiceDefinitions.Owned) > 0 { + return nil, fmt.Errorf("unsupported bundle: apiServiceDefintions are not supported") + } + + if len(rv1.CSV.Spec.WebhookDefinitions) > 0 { + if !r.IsWebhookSupportEnabled { + return nil, fmt.Errorf("unsupported bundle: webhookDefinitions are not supported") + } else if r.CertificateProvider == nil { + return nil, fmt.Errorf("unsupported bundle: webhookDefinitions are not supported: certificate provider is nil") + } + } + + installModes := sets.New(rv1.CSV.Spec.InstallModes...) + if !r.IsSingleOwnNamespaceEnabled && !installModes.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}) { + return nil, fmt.Errorf("unsupported bundle: bundle does not support AllNamespaces install mode") + } + + if !installModes.HasAny( + v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}, + v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}, + v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}, + ) { + return nil, fmt.Errorf("unsupported bundle: bundle must support at least one of [AllNamespaces SingleNamespace OwnNamespace] install modes") + } + + opts := []render.Option{ + render.WithCertificateProvider(r.CertificateProvider), + } + + if r.IsSingleOwnNamespaceEnabled { + bundleConfigBytes := extensionConfigBytes(ext) + // treat no config as empty to properly validate the configuration + // e.g. ensure that validation catches missing required fields + if bundleConfigBytes == nil { + bundleConfigBytes = []byte(`{}`) + } + bundleConfig, err := bundle.UnmarshalConfig(bundleConfigBytes, rv1, ext.Spec.Namespace) + if err != nil { + return nil, fmt.Errorf("invalid bundle configuration: %w", err) + } + + if bundleConfig != nil && bundleConfig.WatchNamespace != nil { + opts = append(opts, render.WithTargetNamespaces(*bundleConfig.WatchNamespace)) + } + } + + return r.BundleRenderer.Render(rv1, ext.Spec.Namespace, opts...) +} + +// RegistryV1HelmChartProvider creates a Helm-Chart from a registry+v1 bundle and its associated ClusterExtension +type RegistryV1HelmChartProvider struct { + ManifestProvider ManifestProvider +} + +func (r *RegistryV1HelmChartProvider) Get(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) { + objs, err := r.ManifestProvider.Get(bundleFS, ext) + if err != nil { + return nil, err + } + + chrt := &chart.Chart{Metadata: &chart.Metadata{}} + // The need to get the underlying bundle in order to extract its annotations + // will go away once with have a bundle interface that can surface the annotations independently of the + // underlying bundle format... + rv1, err := source.FromFS(bundleFS).GetBundle() + if err != nil { + return nil, err + } + chrt.Metadata.Annotations = rv1.CSV.GetAnnotations() + for _, obj := range objs { + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + hash := sha256.Sum256(jsonData) + name := fmt.Sprintf("object-%x.json", hash[0:8]) + + // Some registry+v1 manifests may actually contain Go Template strings + // that are meant to survive and actually persist into etcd (e.g. to be + // used as a templated configuration for another component). In order to + // avoid applying templating logic to registry+v1's static manifests, we + // create the manifests as Files, and then template those files via simple + // Templates. + chrt.Files = append(chrt.Files, &chart.File{ + Name: name, + Data: jsonData, + }) + chrt.Templates = append(chrt.Templates, &chart.File{ + Name: name, + Data: []byte(fmt.Sprintf(`{{.Files.Get "%s"}}`, name)), + }) + } + + return chrt, nil +} + +// ExtensionConfigBytes returns the ClusterExtension configuration input by the user +// through .spec.config as a byte slice. +func extensionConfigBytes(ext *ocv1.ClusterExtension) []byte { + if ext.Spec.Config != nil { + switch ext.Spec.Config.ConfigType { + case ocv1.ClusterExtensionConfigTypeInline: + if ext.Spec.Config.Inline != nil { + return ext.Spec.Config.Inline.Raw + } + } + } + return nil +} diff --git a/internal/operator-controller/applier/provider_test.go b/internal/operator-controller/applier/provider_test.go new file mode 100644 index 0000000000..4ec20beadb --- /dev/null +++ b/internal/operator-controller/applier/provider_test.go @@ -0,0 +1,501 @@ +package applier_test + +import ( + "errors" + "io/fs" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1" + . "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/bundlefs" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_RegistryV1ManifestProvider_Integration(t *testing.T) { + t.Run("surfaces bundle source errors", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{} + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + _, err := provider.Get(fstest.MapFS{}, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "metadata/annotations.yaml: file does not exist") + }) + + t.Run("surfaces bundle renderer errors", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + BundleRenderer: render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return nil, errors.New("some error") + }, + }, + }, + } + + // The contents of the bundle are not important for this tesy, only that it be a valid bundle + // to avoid errors in the deserialization process + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build()).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + _, err := provider.Get(bundleFS, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "some error") + }) + + t.Run("surfaces bundle config unmarshall errors", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + BundleRenderer: render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return nil, nil + }, + }, + }, + // must be true for now as we only unmarshal configuration when this feature is on + // once we go GA and remove IsSingleOwnNamespaceEnabled it's ok to just delete this + IsSingleOwnNamespaceEnabled: true, + } + + // The contents of the bundle are not important for this tesy, only that it be a valid bundle + // to avoid errors in the deserialization process + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build()).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "install-namespace"}`), + }, + }, + }, + } + + _, err := provider.Get(bundleFS, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid bundle configuration") + }) + + t.Run("returns rendered manifests", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + BundleRenderer: registryv1.Renderer, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build()). + WithBundleResource("service.yaml", &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + }, + }).Build() + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + objs, err := provider.Get(bundleFS, ext) + require.NoError(t, err) + + exp := ToUnstructuredT(t, &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Namespace: "install-namespace", + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{}, + }, + }) + + require.Equal(t, []client.Object{exp}, objs) + }) +} + +func Test_RegistryV1ManifestProvider_APIServiceSupport(t *testing.T) { + t.Run("rejects registry+v1 bundles with API service definitions", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{} + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithOwnedAPIServiceDescriptions(v1alpha1.APIServiceDescription{Name: "test-apiservice"}).Build()).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + _, err := provider.Get(bundleFS, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported bundle: apiServiceDefintions are not supported") + }) +} + +func Test_RegistryV1ManifestProvider_WebhookSupport(t *testing.T) { + t.Run("rejects bundles with webhook definitions if support is disabled", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsWebhookSupportEnabled: false, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithWebhookDefinitions(v1alpha1.WebhookDescription{}).Build()).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + _, err := provider.Get(bundleFS, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "webhookDefinitions are not supported") + }) + + t.Run("fails if bundle contains webhook definitions, webhook support is enabled, but the certificate provider is undefined", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsWebhookSupportEnabled: true, + CertificateProvider: nil, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithWebhookDefinitions(v1alpha1.WebhookDescription{}).Build()).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + _, err := provider.Get(bundleFS, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "webhookDefinitions are not supported") + }) + + t.Run("accepts bundles with webhook definitions if support is enabled and a certificate provider is defined", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + CertificateProvider: FakeCertProvider{}, + IsWebhookSupportEnabled: true, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV( + clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions(v1alpha1.WebhookDescription{}).Build()). + Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + _, err := provider.Get(bundleFS, ext) + require.NoError(t, err) + }) +} + +func Test_RegistryV1ManifestProvider_SingleOwnNamespaceSupport(t *testing.T) { + t.Run("rejects bundles without AllNamespaces install mode when Single/OwnNamespace install mode support is disabled", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: false, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build()).Build() + + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + }) + require.Equal(t, "unsupported bundle: bundle does not support AllNamespaces install mode", err.Error()) + }) + + t.Run("rejects bundles without AllNamespaces install mode and with SingleNamespace support when Single/OwnNamespace install mode support is enabled", func(t *testing.T) { + expectedWatchNamespace := "some-namespace" + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: false, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build()).Build() + + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "` + expectedWatchNamespace + `"}`), + }, + }, + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported bundle") + }) + + t.Run("rejects bundles without AllNamespaces install mode and with OwnNamespace support when Single/OwnNamespace install mode support is disabled", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: false, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build()).Build() + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported bundle") + }) + + t.Run("accepts bundles with install modes {SingleNamespace} when the appropriate configuration is given", func(t *testing.T) { + expectedWatchNamespace := "some-namespace" + provider := applier.RegistryV1ManifestProvider{ + BundleRenderer: render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + t.Log("ensure watch namespace is appropriately configured") + require.Equal(t, []string{expectedWatchNamespace}, opts.TargetNamespaces) + return nil, nil + }, + }, + }, + IsSingleOwnNamespaceEnabled: true, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build()).Build() + + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "` + expectedWatchNamespace + `"}`), + }, + }, + }, + }) + require.NoError(t, err) + }) + + t.Run("rejects bundles with {SingleNamespace} install modes when no configuration is given", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: true, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build()).Build() + + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "required field \"watchNamespace\" is missing") + }) + + t.Run("accepts bundles with {OwnNamespace} install modes when the appropriate configuration is given", func(t *testing.T) { + installNamespace := "some-namespace" + provider := applier.RegistryV1ManifestProvider{ + BundleRenderer: render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + t.Log("ensure watch namespace is appropriately configured") + require.Equal(t, []string{installNamespace}, opts.TargetNamespaces) + return nil, nil + }, + }, + }, + IsSingleOwnNamespaceEnabled: true, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build()).Build() + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: installNamespace, + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "` + installNamespace + `"}`), + }, + }, + }, + }) + require.NoError(t, err) + }) + + t.Run("rejects bundles with {OwnNamespace} install modes when no configuration is given", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: true, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build()).Build() + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "required field \"watchNamespace\" is missing") + }) + + t.Run("rejects bundles with {OwnNamespace} install modes when watchNamespace is not install namespace", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: true, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build()).Build() + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "not-install-namespace"}`), + }, + }, + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid 'watchNamespace' \"not-install-namespace\": must be install namespace (install-namespace)") + }) + + t.Run("rejects bundles without AllNamespaces, SingleNamespace, or OwnNamespace install mode support when Single/OwnNamespace install mode support is enabled", func(t *testing.T) { + provider := applier.RegistryV1ManifestProvider{ + IsSingleOwnNamespaceEnabled: true, + } + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV(clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeMultiNamespace).Build()).Build() + _, err := provider.Get(bundleFS, &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + }) + require.Equal(t, "unsupported bundle: bundle must support at least one of [AllNamespaces SingleNamespace OwnNamespace] install modes", err.Error()) + }) +} + +func Test_RegistryV1HelmChartProvider_Integration(t *testing.T) { + t.Run("surfaces bundle source errors", func(t *testing.T) { + provider := applier.RegistryV1HelmChartProvider{ + ManifestProvider: DummyManifestProvider, + } + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + _, err := provider.Get(fstest.MapFS{}, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "metadata/annotations.yaml: file does not exist") + }) + + t.Run("surfaces manifest provider failures", func(t *testing.T) { + provider := applier.RegistryV1HelmChartProvider{ + ManifestProvider: &FakeManifestProvider{ + GetFn: func(bundle fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) { + return nil, errors.New("some error") + }, + }, + } + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + _, err := provider.Get(fstest.MapFS{}, ext) + require.Error(t, err) + require.Contains(t, err.Error(), "some error") + }) +} + +func Test_RegistryV1HelmChartProvider_Chart(t *testing.T) { + provider := applier.RegistryV1HelmChartProvider{ + ManifestProvider: &applier.RegistryV1ManifestProvider{ + BundleRenderer: registryv1.Renderer, + }, + } + + bundleFS := bundlefs.Builder().WithPackageName("test"). + WithCSV( + clusterserviceversion.Builder(). + WithAnnotations(map[string]string{"foo": "bar"}). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + Build()). + WithBundleResource("service.yaml", &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "testService", + }, + }).Build() + + ext := &ocv1.ClusterExtension{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "install-namespace", + }, + } + + chart, err := provider.Get(bundleFS, ext) + require.NoError(t, err) + require.NotNil(t, chart) + require.NotNil(t, chart.Metadata) + + t.Log("Check Chart metadata contains CSV annotations") + require.Equal(t, map[string]string{"foo": "bar"}, chart.Metadata.Annotations) + + t.Log("Check Chart templates have the same number of resources generated by the renderer") + require.Len(t, chart.Templates, 1) +} + +var DummyManifestProvider = &FakeManifestProvider{ + GetFn: func(bundle fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) { + return []client.Object{}, nil + }, +} + +type FakeManifestProvider struct { + GetFn func(bundleFS fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) +} + +func (f *FakeManifestProvider) Get(bundleFS fs.FS, ext *ocv1.ClusterExtension) ([]client.Object, error) { + return f.GetFn(bundleFS, ext) +} diff --git a/internal/operator-controller/authentication/synthetic.go b/internal/operator-controller/authentication/synthetic.go new file mode 100644 index 0000000000..710f2885e8 --- /dev/null +++ b/internal/operator-controller/authentication/synthetic.go @@ -0,0 +1,26 @@ +package authentication + +import ( + "fmt" + + "k8s.io/client-go/transport" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +func syntheticUserName(ext ocv1.ClusterExtension) string { + return fmt.Sprintf("olm:clusterextension:%s", ext.Name) +} + +func syntheticGroups(_ ocv1.ClusterExtension) []string { + return []string{ + "olm:clusterextensions", + } +} + +func SyntheticImpersonationConfig(ext ocv1.ClusterExtension) transport.ImpersonationConfig { + return transport.ImpersonationConfig{ + UserName: syntheticUserName(ext), + Groups: syntheticGroups(ext), + } +} diff --git a/internal/operator-controller/authentication/synthetic_test.go b/internal/operator-controller/authentication/synthetic_test.go new file mode 100644 index 0000000000..2e3f17a07b --- /dev/null +++ b/internal/operator-controller/authentication/synthetic_test.go @@ -0,0 +1,25 @@ +package authentication_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" +) + +func TestSyntheticImpersonationConfig(t *testing.T) { + config := authentication.SyntheticImpersonationConfig(ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ext", + }, + }) + require.Equal(t, "olm:clusterextension:my-ext", config.UserName) + require.Equal(t, []string{ + "olm:clusterextensions", + }, config.Groups) + require.Empty(t, config.UID) + require.Empty(t, config.Extra) +} diff --git a/internal/authentication/tokengetter.go b/internal/operator-controller/authentication/tokengetter.go similarity index 79% rename from internal/authentication/tokengetter.go rename to internal/operator-controller/authentication/tokengetter.go index 70e8f043e5..7870dc8e83 100644 --- a/internal/authentication/tokengetter.go +++ b/internal/operator-controller/authentication/tokengetter.go @@ -2,10 +2,12 @@ package authentication import ( "context" + "fmt" "sync" "time" authenticationv1 "k8s.io/api/authentication/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -19,6 +21,21 @@ type TokenGetter struct { mu sync.RWMutex } +type ServiceAccountNotFoundError struct { + ServiceAccountName string + ServiceAccountNamespace string + Err error +} + +func (e *ServiceAccountNotFoundError) Unwrap() error { + return e.Err +} + +// Error implements the error interface for ServiceAccountNotFoundError. +func (e *ServiceAccountNotFoundError) Error() string { + return fmt.Sprintf("service account \"%s\" not found in namespace \"%s\": unable to authenticate with the Kubernetes cluster.", e.ServiceAccountName, e.ServiceAccountNamespace) +} + type TokenGetterOption func(*TokenGetter) const ( @@ -83,9 +100,12 @@ func (t *TokenGetter) getToken(ctx context.Context, key types.NamespacedName) (* req, err := t.client.ServiceAccounts(key.Namespace).CreateToken(ctx, key.Name, &authenticationv1.TokenRequest{ - Spec: authenticationv1.TokenRequestSpec{ExpirationSeconds: ptr.To[int64](int64(t.expirationDuration / time.Second))}, + Spec: authenticationv1.TokenRequestSpec{ExpirationSeconds: ptr.To(int64(t.expirationDuration / time.Second))}, }, metav1.CreateOptions{}) if err != nil { + if errors.IsNotFound(err) { + return nil, &ServiceAccountNotFoundError{ServiceAccountName: key.Name, ServiceAccountNamespace: key.Namespace} + } return nil, err } return &req.Status, nil diff --git a/internal/authentication/tokengetter_test.go b/internal/operator-controller/authentication/tokengetter_test.go similarity index 91% rename from internal/authentication/tokengetter_test.go rename to internal/operator-controller/authentication/tokengetter_test.go index b9553cac33..663e95f1b3 100644 --- a/internal/authentication/tokengetter_test.go +++ b/internal/operator-controller/authentication/tokengetter_test.go @@ -72,13 +72,15 @@ func TestTokenGetterGet(t *testing.T) { "test-namespace-3", "test-token-3", "failed to get token"}, {"Testing error when getting token from fake client", "test-service-account-4", "test-namespace-4", "error when fetching token", "error when fetching token"}, + {"Testing service account not found", "missing-sa", + "test-namespace-5", "", "service account \"missing-sa\" not found in namespace \"test-namespace-5\": unable to authenticate with the Kubernetes cluster."}, } for _, tc := range tests { got, err := tg.Get(context.Background(), types.NamespacedName{Namespace: tc.namespace, Name: tc.serviceAccountName}) if err != nil { t.Logf("%s: expected: %v, got: %v", tc.testName, tc.want, err) - assert.EqualError(t, err, tc.errorMsg) + assert.EqualError(t, err, tc.errorMsg, "Error message should match expected output") } else { t.Logf("%s: expected: %v, got: %v", tc.testName, tc.want, got) assert.Equal(t, tc.want, got, tc.errorMsg) diff --git a/internal/authentication/tripper.go b/internal/operator-controller/authentication/tripper.go similarity index 100% rename from internal/authentication/tripper.go rename to internal/operator-controller/authentication/tripper.go diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go new file mode 100644 index 0000000000..357268615c --- /dev/null +++ b/internal/operator-controller/authorization/rbac.go @@ -0,0 +1,671 @@ +package authorization + +import ( + "context" + "errors" + "fmt" + "io" + "maps" + "regexp" + "slices" + "sort" + "strings" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/endpoints/request" + rbacinternal "k8s.io/kubernetes/pkg/apis/rbac" + rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" + "k8s.io/kubernetes/pkg/registry/rbac/validation" + rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +type PreAuthorizer interface { + PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) +} + +type ScopedPolicyRules struct { + Namespace string + MissingRules []rbacv1.PolicyRule +} + +var objectVerbs = []string{"get", "patch", "update", "delete"} + +// Here we are splitting collection verbs based on required scope +// NB: this split is tightly coupled to the requirements of the contentmanager, specifically +// its need for cluster-scoped list/watch permissions. +// TODO: We are accepting this coupling for now, but plan to decouple +// TODO: link for above https://github.com/operator-framework/operator-controller/issues/1911 +var namespacedCollectionVerbs = []string{"create"} +var clusterCollectionVerbs = []string{"list", "watch"} + +type rbacPreAuthorizer struct { + authorizer authorizer.Authorizer + ruleResolver validation.AuthorizationRuleResolver + restMapper meta.RESTMapper +} + +func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { + return &rbacPreAuthorizer{ + authorizer: newRBACAuthorizer(cl), + ruleResolver: newRBACRulesResolver(cl), + restMapper: cl.RESTMapper(), + } +} + +// PreAuthorize validates whether the current user/request satisfies the necessary permissions +// as defined by the RBAC policy. It examines the user’s roles, resource identifiers, and +// the intended action to determine if the operation is allowed. +// +// Return Value: +// - nil: indicates that the authorization check passed and the operation is permitted. +// - non-nil error: indicates that an error occurred during the permission evaluation process +// (for example, a failure decoding the manifest or other internal issues). If the evaluation +// completes successfully but identifies missing rules, then a nil error is returned along with +// the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple +// evaluation failures +func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) { + dm, err := a.decodeManifest(manifestReader) + if err != nil { + return nil, err + } + manifestManager := &user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)} + attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager, ext) + + var preAuthEvaluationErrors []error + missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords) + if err != nil { + preAuthEvaluationErrors = append(preAuthEvaluationErrors, err) + } + + ec := a.escalationCheckerFor(dm) + + var parseErrors []error + for _, obj := range dm.rbacObjects() { + if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil { + result, err := parseEscalationErrorForMissingRules(err) + missingRules[obj.GetNamespace()] = append(missingRules[obj.GetNamespace()], result.MissingRules...) + preAuthEvaluationErrors = append(preAuthEvaluationErrors, result.ResolutionErrors) + parseErrors = append(parseErrors, err) + } + } + allMissingPolicyRules := make([]ScopedPolicyRules, 0, len(missingRules)) + + for ns, nsMissingRules := range missingRules { + // NOTE: Although CompactRules is defined to return an error, its current implementation + // never produces a non-nil error. This is because all operations within the function are + // designed to succeed under current conditions. In the future, if more complex rule validations + // are introduced, this behavior may change and proper error handling will be required. + if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil { + missingRules[ns] = compactMissingRules + } + + missingRulesWithDeduplicatedVerbs := make([]rbacv1.PolicyRule, 0, len(missingRules[ns])) + for _, rule := range missingRules[ns] { + verbSet := sets.New[string](rule.Verbs...) + if verbSet.Has("*") { + rule.Verbs = []string{"*"} + } else { + rule.Verbs = sets.List(verbSet) + } + missingRulesWithDeduplicatedVerbs = append(missingRulesWithDeduplicatedVerbs, rule) + } + + sortableRules := rbacv1helpers.SortableRuleSlice(missingRulesWithDeduplicatedVerbs) + + sort.Sort(sortableRules) + allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules}) + } + + // sort allMissingPolicyRules alphabetically by namespace + slices.SortFunc(allMissingPolicyRules, func(a, b ScopedPolicyRules) int { + return strings.Compare(a.Namespace, b.Namespace) + }) + + var errs []error + if parseErr := errors.Join(parseErrors...); parseErr != nil { + errs = append(errs, fmt.Errorf("failed to parse escalation check error strings: %v", parseErr)) + } + if preAuthEvaluationErrors := errors.Join(preAuthEvaluationErrors...); preAuthEvaluationErrors != nil { + errs = append(errs, fmt.Errorf("failed to resolve or evaluate permissions: %v", preAuthEvaluationErrors)) + } + if len(errs) > 0 { + return allMissingPolicyRules, fmt.Errorf("missing rules may be incomplete: %w", errors.Join(errs...)) + } + return allMissingPolicyRules, nil +} + +func (a *rbacPreAuthorizer) escalationCheckerFor(dm *decodedManifest) escalationChecker { + ec := escalationChecker{ + authorizer: a.authorizer, + ruleResolver: a.ruleResolver, + extraClusterRoles: dm.clusterRoles, + extraRoles: dm.roles, + } + return ec +} + +func (a *rbacPreAuthorizer) decodeManifest(manifestReader io.Reader) (*decodedManifest, error) { + dm := &decodedManifest{ + gvrs: map[schema.GroupVersionResource][]types.NamespacedName{}, + clusterRoles: map[client.ObjectKey]rbacv1.ClusterRole{}, + roles: map[client.ObjectKey]rbacv1.Role{}, + clusterRoleBindings: map[client.ObjectKey]rbacv1.ClusterRoleBinding{}, + roleBindings: map[client.ObjectKey]rbacv1.RoleBinding{}, + } + var ( + i int + errs []error + decoder = apimachyaml.NewYAMLOrJSONDecoder(manifestReader, 1024) + ) + for { + var uObj unstructured.Unstructured + err := decoder.Decode(&uObj) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest: %w", i, err)) + continue + } + gvk := uObj.GroupVersionKind() + restMapping, err := a.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + var objName string + if name := uObj.GetName(); name != "" { + objName = fmt.Sprintf(" (name: %s)", name) + } + + errs = append( + errs, + fmt.Errorf("could not get REST mapping for object %d in manifest with GVK %s%s: %w", i, gvk, objName, err), + ) + continue + } + + gvr := restMapping.Resource + dm.gvrs[gvr] = append(dm.gvrs[gvr], client.ObjectKeyFromObject(&uObj)) + + switch restMapping.Resource.GroupResource() { + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterroles"}: + obj := &rbacv1.ClusterRole{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as ClusterRole: %w", i, err)) + continue + } + dm.clusterRoles[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterrolebindings"}: + obj := &rbacv1.ClusterRoleBinding{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as ClusterRoleBinding: %w", i, err)) + continue + } + dm.clusterRoleBindings[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "roles"}: + obj := &rbacv1.Role{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as Role: %w", i, err)) + continue + } + dm.roles[client.ObjectKeyFromObject(obj)] = *obj + case schema.GroupResource{Group: rbacv1.GroupName, Resource: "rolebindings"}: + obj := &rbacv1.RoleBinding{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), obj); err != nil { + errs = append(errs, fmt.Errorf("could not decode object %d in manifest as RoleBinding: %w", i, err)) + continue + } + dm.roleBindings[client.ObjectKeyFromObject(obj)] = *obj + } + } + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return dm, nil +} + +func (a *rbacPreAuthorizer) authorizeAttributesRecords(ctx context.Context, attributesRecords []authorizer.AttributesRecord) (map[string][]rbacv1.PolicyRule, error) { + var ( + missingRules = map[string][]rbacv1.PolicyRule{} + errs []error + ) + for _, ar := range attributesRecords { + allow, err := a.attributesAllowed(ctx, ar) + if err != nil { + errs = append(errs, err) + continue + } + if !allow { + missingRules[ar.Namespace] = append(missingRules[ar.Namespace], policyRuleFromAttributesRecord(ar)) + } + } + return missingRules, errors.Join(errs...) +} + +func (a *rbacPreAuthorizer) attributesAllowed(ctx context.Context, attributesRecord authorizer.AttributesRecord) (bool, error) { + decision, reason, err := a.authorizer.Authorize(ctx, attributesRecord) + if err != nil { + if reason != "" { + return false, fmt.Errorf("%s: %w", reason, err) + } + return false, err + } + return decision == authorizer.DecisionAllow, nil +} + +func policyRuleFromAttributesRecord(attributesRecord authorizer.AttributesRecord) rbacv1.PolicyRule { + pr := rbacv1.PolicyRule{} + if attributesRecord.Verb != "" { + pr.Verbs = []string{attributesRecord.Verb} + } + if !attributesRecord.ResourceRequest { + pr.NonResourceURLs = []string{attributesRecord.Path} + return pr + } + + pr.APIGroups = []string{attributesRecord.APIGroup} + if attributesRecord.Name != "" { + pr.ResourceNames = []string{attributesRecord.Name} + } + + r := attributesRecord.Resource + if attributesRecord.Subresource != "" { + r += "/" + attributesRecord.Subresource + } + pr.Resources = []string{r} + + return pr +} + +type decodedManifest struct { + gvrs map[schema.GroupVersionResource][]types.NamespacedName + clusterRoles map[client.ObjectKey]rbacv1.ClusterRole + roles map[client.ObjectKey]rbacv1.Role + clusterRoleBindings map[client.ObjectKey]rbacv1.ClusterRoleBinding + roleBindings map[client.ObjectKey]rbacv1.RoleBinding +} + +func (dm *decodedManifest) rbacObjects() []client.Object { + objects := make([]client.Object, 0, len(dm.clusterRoles)+len(dm.roles)+len(dm.clusterRoleBindings)+len(dm.roleBindings)) + for obj := range maps.Values(dm.clusterRoles) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.roles) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.clusterRoleBindings) { + objects = append(objects, &obj) + } + for obj := range maps.Values(dm.roleBindings) { + objects = append(objects, &obj) + } + return objects +} + +func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info, ext *ocv1.ClusterExtension) []authorizer.AttributesRecord { + var attributeRecords []authorizer.AttributesRecord + + for gvr, keys := range dm.gvrs { + namespaces := sets.New[string]() + for _, k := range keys { + namespaces.Insert(k.Namespace) + // generate records for object-specific verbs (get, update, patch, delete) in their respective namespaces + for _, v := range objectVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: k.Namespace, + Name: k.Name, + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } + } + // generate records for namespaced collection verbs (create) for each relevant namespace + for _, ns := range sets.List(namespaces) { + for _, v := range namespacedCollectionVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: ns, + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } + } + // generate records for cluster-scoped collection verbs (list, watch) required by contentmanager + for _, v := range clusterCollectionVerbs { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Namespace: corev1.NamespaceAll, // check cluster scope + APIGroup: gvr.Group, + APIVersion: gvr.Version, + Resource: gvr.Resource, + ResourceRequest: true, + Verb: v, + }) + } + + for _, verb := range []string{"update"} { + attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ + User: manifestManager, + Name: ext.Name, + APIGroup: ext.GroupVersionKind().Group, + APIVersion: ext.GroupVersionKind().Version, + Resource: "clusterextensions/finalizers", + ResourceRequest: true, + Verb: verb, + }) + } + } + return attributeRecords +} + +func newRBACAuthorizer(cl client.Client) authorizer.Authorizer { + rg := &rbacGetter{cl: cl} + return rbac.New(rg, rg, rg, rg) +} + +type rbacGetter struct { + cl client.Client +} + +func (r rbacGetter) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { + var clusterRoleBindingsList rbacv1.ClusterRoleBindingList + if err := r.cl.List(ctx, &clusterRoleBindingsList); err != nil { + return nil, err + } + return toPtrSlice(clusterRoleBindingsList.Items), nil +} + +func (r rbacGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { + var clusterRole rbacv1.ClusterRole + if err := r.cl.Get(ctx, client.ObjectKey{Name: name}, &clusterRole); err != nil { + return nil, err + } + return &clusterRole, nil +} + +func (r rbacGetter) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { + var roleBindingsList rbacv1.RoleBindingList + if err := r.cl.List(ctx, &roleBindingsList, client.InNamespace(namespace)); err != nil { + return nil, err + } + return toPtrSlice(roleBindingsList.Items), nil +} + +func (r rbacGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { + var role rbacv1.Role + if err := r.cl.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &role); err != nil { + return nil, err + } + return &role, nil +} + +func newRBACRulesResolver(cl client.Client) validation.AuthorizationRuleResolver { + rg := &rbacGetter{cl: cl} + return validation.NewDefaultRuleResolver(rg, rg, rg, rg) +} + +type escalationChecker struct { + authorizer authorizer.Authorizer + ruleResolver validation.AuthorizationRuleResolver + extraRoles map[types.NamespacedName]rbacv1.Role + extraClusterRoles map[types.NamespacedName]rbacv1.ClusterRole +} + +func (ec *escalationChecker) checkEscalation(ctx context.Context, manifestManager user.Info, obj client.Object) error { + ctx = request.WithUser(request.WithNamespace(ctx, obj.GetNamespace()), manifestManager) + switch v := obj.(type) { + case *rbacv1.Role: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "roles", IsResourceRequest: true}) + return ec.checkRoleEscalation(ctx, v) + case *rbacv1.RoleBinding: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "rolebindings", IsResourceRequest: true}) + return ec.checkRoleBindingEscalation(ctx, v) + case *rbacv1.ClusterRole: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterroles", IsResourceRequest: true}) + return ec.checkClusterRoleEscalation(ctx, v) + case *rbacv1.ClusterRoleBinding: + ctx = request.WithRequestInfo(ctx, &request.RequestInfo{APIGroup: rbacv1.GroupName, Resource: "clusterrolebindings", IsResourceRequest: true}) + return ec.checkClusterRoleBindingEscalation(ctx, v) + default: + return fmt.Errorf("unknown object type %T", v) + } +} + +func (ec *escalationChecker) checkClusterRoleEscalation(ctx context.Context, clusterRole *rbacv1.ClusterRole) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } + + // to set the aggregation rule, since it can gather anything, requires * on *.* + if hasAggregationRule(clusterRole) { + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, fullAuthority); err != nil { + return fmt.Errorf("must have cluster-admin privileges to use an aggregationRule: %w", err) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, clusterRole.Rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkClusterRoleBindingEscalation(ctx context.Context, clusterRoleBinding *rbacv1.ClusterRoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } + + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&clusterRoleBinding.RoleRef, &roleRef, nil) + if err != nil { + return err + } + + if rbacregistry.BindingAuthorized(ctx, roleRef, metav1.NamespaceNone, ec.authorizer) { + return nil + } + + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, clusterRoleBinding.RoleRef, metav1.NamespaceNone) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + if clusterRoleBinding.RoleRef.Kind == "ClusterRole" { + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: clusterRoleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkRoleEscalation(ctx context.Context, role *rbacv1.Role) error { + if rbacregistry.EscalationAllowed(ctx) || rbacregistry.RoleEscalationAuthorized(ctx, ec.authorizer) { + return nil + } + + rules := role.Rules + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +func (ec *escalationChecker) checkRoleBindingEscalation(ctx context.Context, roleBinding *rbacv1.RoleBinding) error { + if rbacregistry.EscalationAllowed(ctx) { + return nil + } + + roleRef := rbacinternal.RoleRef{} + err := rbacv1helpers.Convert_v1_RoleRef_To_rbac_RoleRef(&roleBinding.RoleRef, &roleRef, nil) + if err != nil { + return err + } + if rbacregistry.BindingAuthorized(ctx, roleRef, roleBinding.Namespace, ec.authorizer) { + return nil + } + + rules, err := ec.ruleResolver.GetRoleReferenceRules(ctx, roleBinding.RoleRef, roleBinding.Namespace) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + switch roleRef.Kind { + case "ClusterRole": + if manifestClusterRole, ok := ec.extraClusterRoles[types.NamespacedName{Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestClusterRole.Rules...) + } + case "Role": + if manifestRole, ok := ec.extraRoles[types.NamespacedName{Namespace: roleBinding.Namespace, Name: roleBinding.RoleRef.Name}]; ok { + rules = append(rules, manifestRole.Rules...) + } + } + + if err := validation.ConfirmNoEscalation(ctx, ec.ruleResolver, rules); err != nil { + return err + } + return nil +} + +var fullAuthority = []rbacv1.PolicyRule{ + {Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, + {Verbs: []string{"*"}, NonResourceURLs: []string{"*"}}, +} + +var ( + errRegex = regexp.MustCompile(`(?s)^user ".*" \(groups=.*\) is attempting to grant RBAC permissions not currently held:\n([^;]+)(?:; resolution errors: (.*))?$`) + ruleRegex = regexp.MustCompile(`{([^}]*)}`) + itemRegex = regexp.MustCompile(`"[^"]*"`) +) + +type parseResult struct { + MissingRules []rbacv1.PolicyRule + ResolutionErrors error +} + +// TODO: Investigate replacing this regex parsing with structured error handling once there are +// +// structured RBAC errors introduced by https://github.com/kubernetes/kubernetes/pull/130955. +// +// parseEscalationErrorForMissingRules attempts to extract specific RBAC permissions +// that were denied due to escalation prevention from a given error's text. +// It returns the list of extracted PolicyRules and an error detailing the escalation attempt +// and any resolution errors found. +// Note: If parsing is successful, the returned error is derived from the *input* error's +// message, not an error encountered during the parsing process itself. If parsing fails due to an unexpected +// error format, a distinct parsing error is returned. +func parseEscalationErrorForMissingRules(ecError error) (*parseResult, error) { + var ( + result = &parseResult{} + parseErrors []error + ) + + // errRegex captures the missing permissions and optionally resolution errors from an escalation error message + // Group 1: The list of missing permissions + // Group 2: Optional resolution errors + errString := ecError.Error() + errMatches := errRegex.FindStringSubmatch(errString) // Use FindStringSubmatch for single match expected + + // Check if the main error message pattern was matched and captured the required groups + // We expect at least 3 elements: full match, missing permissions, resolution errors (can be empty) + if len(errMatches) != 3 { + // The error format doesn't match the expected pattern for escalation errors + return &parseResult{}, fmt.Errorf("unexpected format of escalation check error string: %q", errString) + } + missingPermissionsStr := errMatches[1] + if resolutionErrorsStr := errMatches[2]; resolutionErrorsStr != "" { + result.ResolutionErrors = errors.New(resolutionErrorsStr) + } + + // Extract permissions using permRegex from the captured permissions string (Group 1) + for _, rule := range ruleRegex.FindAllString(missingPermissionsStr, -1) { + pr, err := parseCompactRuleString(rule) + if err != nil { + parseErrors = append(parseErrors, err) + continue + } + result.MissingRules = append(result.MissingRules, *pr) + } + // Return the extracted permissions and the constructed error message + return result, errors.Join(parseErrors...) +} + +func parseCompactRuleString(rule string) (*rbacv1.PolicyRule, error) { + var fields []string + if ruleText := rule[1 : len(rule)-1]; ruleText != "" { + fields = mapSlice(strings.Split(ruleText, ","), func(in string) string { + return strings.TrimSpace(in) + }) + } + var pr rbacv1.PolicyRule + for _, item := range fields { + field, valuesStr, ok := strings.Cut(item, ":") + if !ok { + return nil, fmt.Errorf("unexpected item %q: expected :[...]", item) + } + values := mapSlice(itemRegex.FindAllString(valuesStr, -1), func(in string) string { + return strings.Trim(in, `"`) + }) + switch field { + case "APIGroups": + pr.APIGroups = values + case "Resources": + pr.Resources = values + case "ResourceNames": + pr.ResourceNames = values + case "NonResourceURLs": + pr.NonResourceURLs = values + case "Verbs": + pr.Verbs = values + default: + return nil, fmt.Errorf("unexpected item %q: unknown field: %q", item, field) + } + } + return &pr, nil +} + +func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool { + // Currently, an aggregation rule is considered present only if it has one or more selectors. + // An empty slice of ClusterRoleSelectors means no selectors were provided, + // which does NOT imply "match all." + return clusterRole.AggregationRule != nil && len(clusterRole.AggregationRule.ClusterRoleSelectors) > 0 +} + +func mapSlice[I, O any](in []I, f func(I) O) []O { + out := make([]O, len(in)) + for i := range in { + out[i] = f(in[i]) + } + return out +} + +func toPtrSlice[V any](in []V) []*V { + out := make([]*V, len(in)) + for i := range in { + out[i] = &in[i] + } + return out +} diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go new file mode 100644 index 0000000000..8e3d47687d --- /dev/null +++ b/internal/operator-controller/authorization/rbac_test.go @@ -0,0 +1,748 @@ +package authorization + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta/testrestmapper" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kubernetes/pkg/registry/rbac/validation" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +var ( + testManifest = `apiVersion: v1 +kind: Service +metadata: + name: test-service + namespace: test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: test-namespace + ` + + testManifestMultiNamespace = `apiVersion: v1 +kind: Service +metadata: + name: test-service + namespace: test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: test-namespace +--- +kind: Service +metadata: + name: test-service + namespace: a-test-namespace +spec: + clusterIP: None +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-extension-role + namespace: a-test-namespace +rules: +- apiGroups: ["*"] + resources: [serviceaccounts] + verbs: [watch] +- apiGroups: ["*"] + resources: [certificates] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-extension-binding + namespace: a-test-namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-extension-role +subjects: +- kind: ServiceAccount + name: test-serviceaccount + namespace: a-test-namespace + ` + + saName = "test-serviceaccount" + ns = "test-namespace" + exampleClusterExtension = ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-extension"}, + Spec: ocv1.ClusterExtensionSpec{ + Namespace: ns, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: saName, + }, + }, + } + + objects = []client.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + }, + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: saName, + Namespace: ns, + }, + }, + RoleRef: rbacv1.RoleRef{ + Name: "admin-clusterrole", + Kind: "ClusterRole", + }, + }, + &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-serviceaccount", + Namespace: "test-namespace", + }, + }, + } + + privilegedClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"*"}, + Verbs: []string{"*"}, + }, + }, + } + + limitedClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{""}, + Verbs: []string{""}, + }, + }, + } + + escalatingClusterRole = &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-clusterrole", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts", "services", "clusterextensions/finalizers"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "clusterroles", "rolebindings", "clusterrolebindings"}, + Verbs: []string{"get", "patch", "watch", "list", "create", "update", "delete", "escalate", "bind"}, + }, + }, + } + + expectedSingleNamespaceMissingRules = []ScopedPolicyRules{ + { + Namespace: "", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"update"}, + APIGroups: []string{""}, + Resources: []string{"clusterextensions/finalizers"}, + ResourceNames: []string{"test-cluster-extension"}, + NonResourceURLs: []string(nil), + }, + }, + }, + { + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"services"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + }, + } + + expectedMultiNamespaceMissingRules = []ScopedPolicyRules{ + { + Namespace: "", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"update"}, + APIGroups: []string{""}, + Resources: []string{"clusterextensions/finalizers"}, + ResourceNames: []string{"test-cluster-extension"}, + NonResourceURLs: []string(nil), + }, + }, + }, + { + Namespace: "a-test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"services"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + }, + { + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"services"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + }, + } +) + +func setupFakeClient(role client.Object) client.Client { + s := runtime.NewScheme() + _ = corev1.AddToScheme(s) + _ = rbacv1.AddToScheme(s) + restMapper := testrestmapper.TestOnlyStaticRESTMapper(s) + fakeClientBuilder := fake.NewClientBuilder().WithObjects(append(objects, role)...).WithRESTMapper(restMapper) + return fakeClientBuilder.Build() +} + +func TestPreAuthorize_Success(t *testing.T) { + t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { + fakeClient := setupFakeClient(privilegedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, []ScopedPolicyRules{}, missingRules) + }) +} + +func TestPreAuthorize_MissingRBAC(t *testing.T) { + t.Run("preauthorize fails and finds missing rbac rules", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, expectedSingleNamespaceMissingRules, missingRules) + }) +} + +func TestPreAuthorizeMultiNamespace_MissingRBAC(t *testing.T) { + t.Run("preauthorize fails and finds missing rbac rules in multiple namespaces", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifestMultiNamespace)) + require.NoError(t, err) + require.Equal(t, expectedMultiNamespaceMissingRules, missingRules) + }) +} + +func TestPreAuthorize_CheckEscalation(t *testing.T) { + t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { + fakeClient := setupFakeClient(escalatingClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient) + missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, []ScopedPolicyRules{}, missingRules) + }) +} + +// TestParseEscalationErrorForMissingRules Are tests with respect to https://github.com/kubernetes/api/blob/e8d4d542f6a9a16a694bfc8e3b8cd1557eecfc9d/rbac/v1/types.go#L49-L74 +// Goal is: prove the regex works as planned AND that if the error messages ever change we'll learn about it with these tests +func TestParseEscalationErrorForMissingRules_ParsingLogic(t *testing.T) { + testCases := []struct { + name string + inputError error + expectedResult *parseResult + expectError require.ErrorAssertionFunc + }{ + { + name: "One Missing Resource Rule", + inputError: errors.New(`user "test-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["apps"], Resources:["deployments"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"get"}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Multiple Missing Rules (Resource + NonResource)", + inputError: errors.New(`user "sa" (groups=["system:authenticated"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["pods"], Verbs:["list" "watch"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list", "watch"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + }, + }, + expectError: require.NoError, + }, + { + name: "One Missing Rule with Resolution Errors", + inputError: errors.New(`user "test-admin" (groups=["system:masters"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["batch"], Resources:["jobs"], Verbs:["create"]}; resolution errors: [role "missing-role" not found]`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"batch"}, Resources: []string{"jobs"}, Verbs: []string{"create"}}, + }, + ResolutionErrors: errors.New(`[role "missing-role" not found]`), + }, + expectError: require.NoError, + }, + { + name: "Multiple Missing Rules with Resolution Errors", + inputError: errors.New(`user "another-user" (groups=[]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["secrets"], Verbs:["get"]} +{APIGroups:[""], Resources:["configmaps"], Verbs:["list"]}; resolution errors: [clusterrole "missing-clusterrole" not found, role "other-missing" not found]`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"list"}}, + }, + ResolutionErrors: errors.New(`[clusterrole "missing-clusterrole" not found, role "other-missing" not found]`), + }, + expectError: require.NoError, + }, + { + name: "Missing Rule (All Resource Fields)", + inputError: errors.New(`user "resource-name-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["extensions"], Resources:["ingresses"], ResourceNames:["my-ingress"], Verbs:["update" "patch"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"extensions"}, Resources: []string{"ingresses"}, ResourceNames: []string{"my-ingress"}, Verbs: []string{"update", "patch"}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Missing Rule (No ResourceNames)", + inputError: errors.New(`user "no-res-name-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["networking.k8s.io"], Resources:["networkpolicies"], Verbs:["watch"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"watch"}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Missing Rule (NonResourceURLs only)", + inputError: errors.New(`user "url-user" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{NonResourceURLs:["/version" "/apis"], Verbs:["get"]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {NonResourceURLs: []string{"/version", "/apis"}, Verbs: []string{"get"}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Unexpected Format", + inputError: errors.New("some completely different error message that doesn't match"), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "unexpected format of escalation check error string") + }, + }, + { + name: "Empty Permissions String", + inputError: errors.New(`user "empty-perms" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +`), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "unexpected format of escalation check error string") + }, + }, + { + name: "Rule with Empty Strings in lists", + inputError: errors.New(`user "empty-strings" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:["" "apps"], Resources:["" "deployments"], Verbs:["get" ""]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{"", "apps"}, Resources: []string{"", "deployments"}, Verbs: []string{"get", ""}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Rule with Only Empty Verb", + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["pods"], Verbs:[""]}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{""}}, + }, + }, + expectError: require.NoError, + }, + { + name: "Rule with no fields", + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{}`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{{}}, + }, + expectError: require.NoError, + }, + { + name: "Rule with no colon separator", + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources, Verbs:["get"]} +`), + expectedResult: &parseResult{}, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, `unexpected item "Resources": expected :[...]`) + }, + }, + { + name: "Rule with unknown field", + inputError: errors.New(`user "empty-verb" (groups=["test"]) is attempting to grant RBAC permissions not currently held: +{FooBar:["baz"]} +{APIGroups:[""], Resources:["secrets"], Verbs:["get"]} +`), + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}}, + }, + }, + expectError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, `unknown field: "FooBar"`) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rules, err := parseEscalationErrorForMissingRules(tc.inputError) + tc.expectError(t, err) + require.Equal(t, tc.expectedResult, rules) + }) + } +} + +func TestParseEscalationErrorForMissingRules_KubernetesCompatibility(t *testing.T) { + testCases := []struct { + name string + ruleResolver validation.AuthorizationRuleResolver + wantRules []rbacv1.PolicyRule + expectedErrorString string + expectedResult *parseResult + }{ + { + name: "missing rules", + ruleResolver: mockRulesResolver{ + rules: []rbacv1.PolicyRule{}, + err: nil, + }, + wantRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments", "replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz", "/livez"}, Verbs: []string{"get", "post"}}, + }, + expectedErrorString: `user "user" (groups=["a" "b"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["configmaps"], Verbs:["get" "list" "watch"]} +{APIGroups:[""], Resources:["secrets"], ResourceNames:["test-secret"], Verbs:["get"]} +{APIGroups:["apps"], Resources:["deployments"], Verbs:["create" "update" "patch" "delete"]} +{APIGroups:["apps"], Resources:["replicasets"], Verbs:["create" "update" "patch" "delete"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]} +{NonResourceURLs:["/healthz"], Verbs:["post"]} +{NonResourceURLs:["/livez"], Verbs:["get"]} +{NonResourceURLs:["/livez"], Verbs:["post"]}`, + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {APIGroups: []string{"apps"}, Resources: []string{"replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"post"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"post"}}, + }, + }, + }, + { + name: "resolution failure", + ruleResolver: mockRulesResolver{ + rules: []rbacv1.PolicyRule{}, + err: errors.New("resolution error"), + }, + wantRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments", "replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz", "/livez"}, Verbs: []string{"get", "post"}}, + }, + expectedErrorString: `user "user" (groups=["a" "b"]) is attempting to grant RBAC permissions not currently held: +{APIGroups:[""], Resources:["configmaps"], Verbs:["get" "list" "watch"]} +{APIGroups:[""], Resources:["secrets"], ResourceNames:["test-secret"], Verbs:["get"]} +{APIGroups:["apps"], Resources:["deployments"], Verbs:["create" "update" "patch" "delete"]} +{APIGroups:["apps"], Resources:["replicasets"], Verbs:["create" "update" "patch" "delete"]} +{NonResourceURLs:["/healthz"], Verbs:["get"]} +{NonResourceURLs:["/healthz"], Verbs:["post"]} +{NonResourceURLs:["/livez"], Verbs:["get"]} +{NonResourceURLs:["/livez"], Verbs:["post"]}; resolution errors: [resolution error]`, + expectedResult: &parseResult{ + MissingRules: []rbacv1.PolicyRule{ + {APIGroups: []string{""}, Resources: []string{"configmaps"}, Verbs: []string{"get", "list", "watch"}}, + {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"get"}, ResourceNames: []string{"test-secret"}}, + {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {APIGroups: []string{"apps"}, Resources: []string{"replicasets"}, Verbs: []string{"create", "update", "patch", "delete"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/healthz"}, Verbs: []string{"post"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"get"}}, + {NonResourceURLs: []string{"/livez"}, Verbs: []string{"post"}}, + }, + ResolutionErrors: errors.New("[resolution error]"), + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := request.WithUser(request.WithNamespace(context.Background(), "namespace"), &user.DefaultInfo{ + Name: "user", + Groups: []string{"a", "b"}, + }) + + // Let's actually call the upstream function that generates and returns the + // error message that we are attempting to parse correctly. The hope is that + // these tests will start failing if we bump to a new version of kubernetes + // that causes our parsing logic to be incorrect. + err := validation.ConfirmNoEscalation(ctx, tc.ruleResolver, tc.wantRules) + require.Error(t, err) + require.Equal(t, tc.expectedErrorString, err.Error()) + + res, err := parseEscalationErrorForMissingRules(err) + require.NoError(t, err) + require.Equal(t, tc.expectedResult, res) + }) + } +} + +type mockRulesResolver struct { + rules []rbacv1.PolicyRule + err error +} + +func (m mockRulesResolver) GetRoleReferenceRules(ctx context.Context, roleRef rbacv1.RoleRef, namespace string) ([]rbacv1.PolicyRule, error) { + panic("unimplemented") +} + +func (m mockRulesResolver) RulesFor(ctx context.Context, user user.Info, namespace string) ([]rbacv1.PolicyRule, error) { + return m.rules, m.err +} + +func (m mockRulesResolver) VisitRulesFor(ctx context.Context, user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) { + panic("unimplemented") +} diff --git a/internal/bundleutil/bundle.go b/internal/operator-controller/bundleutil/bundle.go similarity index 86% rename from internal/bundleutil/bundle.go rename to internal/operator-controller/bundleutil/bundle.go index 38eb300478..e123680687 100644 --- a/internal/bundleutil/bundle.go +++ b/internal/operator-controller/bundleutil/bundle.go @@ -9,7 +9,7 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) func GetVersion(b declcfg.Bundle) (*bsemver.Version, error) { @@ -30,8 +30,8 @@ func GetVersion(b declcfg.Bundle) (*bsemver.Version, error) { } // MetadataFor returns a BundleMetadata for the given bundle name and version. -func MetadataFor(bundleName string, bundleVersion bsemver.Version) ocv1alpha1.BundleMetadata { - return ocv1alpha1.BundleMetadata{ +func MetadataFor(bundleName string, bundleVersion bsemver.Version) ocv1.BundleMetadata { + return ocv1.BundleMetadata{ Name: bundleName, Version: bundleVersion.String(), } diff --git a/internal/bundleutil/bundle_test.go b/internal/operator-controller/bundleutil/bundle_test.go similarity index 93% rename from internal/bundleutil/bundle_test.go rename to internal/operator-controller/bundleutil/bundle_test.go index 61ca0ea207..77b7e3bbe6 100644 --- a/internal/bundleutil/bundle_test.go +++ b/internal/operator-controller/bundleutil/bundle_test.go @@ -9,7 +9,7 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - "github.com/operator-framework/operator-controller/internal/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" ) func TestGetVersion(t *testing.T) { diff --git a/internal/operator-controller/catalogmetadata/cache/cache.go b/internal/operator-controller/catalogmetadata/cache/cache.go new file mode 100644 index 0000000000..8bcfff10fc --- /dev/null +++ b/internal/operator-controller/catalogmetadata/cache/cache.go @@ -0,0 +1,166 @@ +package cache + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "sync" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client" +) + +var _ client.Cache = &filesystemCache{} + +func NewFilesystemCache(cachePath string) *filesystemCache { + return &filesystemCache{ + cachePath: cachePath, + mutex: sync.RWMutex{}, + cacheDataByCatalogName: map[string]cacheData{}, + } +} + +// cacheData holds information about a catalog +// other than it's contents that is used for +// making decisions on when to attempt to refresh +// the cache. +type cacheData struct { + Ref string + Error error +} + +// FilesystemCache is a cache that +// uses the local filesystem for caching +// catalog contents. +type filesystemCache struct { + mutex sync.RWMutex + cachePath string + cacheDataByCatalogName map[string]cacheData +} + +// Put writes content from source to the filesystem and stores errToCache +// for a specified catalog name and version (resolvedRef). +// +// Method behaviour is as follows: +// - If successfully populated cache for catalogName and resolvedRef exists, +// errToCache is ignored and existing cache returned with nil error +// - If existing cache for catalogName and resolvedRef exists but +// is populated with an error, update the cache with either +// new content from source or errToCache. +// - If cache doesn't exist, populate it with either new content +// from source or errToCache. +// +// This cache implementation tracks only one version of cache per catalog, +// so Put will override any existing cache on the filesystem for catalogName +// if resolvedRef does not match the one which is already tracked. +func (fsc *filesystemCache) Put(catalogName, resolvedRef string, source io.Reader, errToCache error) (fs.FS, error) { + fsc.mutex.Lock() + defer fsc.mutex.Unlock() + + var cacheFS fs.FS + if errToCache == nil { + cacheFS, errToCache = fsc.writeFS(catalogName, source) + } + fsc.cacheDataByCatalogName[catalogName] = cacheData{ + Ref: resolvedRef, + Error: errToCache, + } + + return cacheFS, errToCache +} + +func (fsc *filesystemCache) writeFS(catalogName string, source io.Reader) (fs.FS, error) { + cacheDir := fsc.cacheDir(catalogName) + + tmpDir, err := os.MkdirTemp(fsc.cachePath, fmt.Sprintf(".%s-", catalogName)) + if err != nil { + return nil, fmt.Errorf("error creating temporary directory to unpack catalog metadata: %v", err) + } + + if err := declcfg.WalkMetasReader(source, func(meta *declcfg.Meta, err error) error { + if err != nil { + return fmt.Errorf("error parsing catalog contents: %v", err) + } + pkgName := meta.Package + if meta.Schema == declcfg.SchemaPackage { + pkgName = meta.Name + } + metaName := meta.Name + if meta.Name == "" { + metaName = meta.Schema + } + metaPath := filepath.Join(tmpDir, pkgName, meta.Schema, metaName+".json") + if err := os.MkdirAll(filepath.Dir(metaPath), os.ModePerm); err != nil { + return fmt.Errorf("error creating directory for catalog metadata: %v", err) + } + if err := os.WriteFile(metaPath, meta.Blob, 0600); err != nil { + return fmt.Errorf("error writing catalog metadata to file: %v", err) + } + return nil + }); err != nil { + return nil, err + } + + if err := os.RemoveAll(cacheDir); err != nil { + return nil, fmt.Errorf("error removing old cache directory: %v", err) + } + if err := os.Rename(tmpDir, cacheDir); err != nil { + return nil, fmt.Errorf("error moving temporary directory to cache directory: %v", err) + } + + return os.DirFS(cacheDir), nil +} + +// Get returns cache for a specified catalog name and version (resolvedRef). +// +// Method behaviour is as follows: +// - If cache exists, it returns a non-nil fs.FS and nil error +// - If cache doesn't exist, it returns nil fs.FS and nil error +// - If there was an error during cache population, +// it returns nil fs.FS and the error from the cache population. +// In other words - cache population errors are also cached. +func (fsc *filesystemCache) Get(catalogName, resolvedRef string) (fs.FS, error) { + fsc.mutex.RLock() + defer fsc.mutex.RUnlock() + return fsc.get(catalogName, resolvedRef) +} + +func (fsc *filesystemCache) get(catalogName, resolvedRef string) (fs.FS, error) { + cacheDir := fsc.cacheDir(catalogName) + if data, ok := fsc.cacheDataByCatalogName[catalogName]; ok { + if resolvedRef == data.Ref { + if data.Error != nil { + return nil, data.Error + } + return os.DirFS(cacheDir), nil + } + } + + return nil, nil +} + +// Remove deletes cache directory for a given catalog from the filesystem +func (fsc *filesystemCache) Remove(catalogName string) error { + cacheDir := fsc.cacheDir(catalogName) + + fsc.mutex.Lock() + defer fsc.mutex.Unlock() + + if _, exists := fsc.cacheDataByCatalogName[catalogName]; !exists { + return nil + } + + if err := os.RemoveAll(cacheDir); err != nil { + return fmt.Errorf("error removing cache directory: %v", err) + } + + delete(fsc.cacheDataByCatalogName, catalogName) + return nil +} + +func (fsc *filesystemCache) cacheDir(catalogName string) string { + return filepath.Join(fsc.cachePath, catalogName) +} diff --git a/internal/operator-controller/catalogmetadata/cache/cache_test.go b/internal/operator-controller/catalogmetadata/cache/cache_test.go new file mode 100644 index 0000000000..ccc796082f --- /dev/null +++ b/internal/operator-controller/catalogmetadata/cache/cache_test.go @@ -0,0 +1,231 @@ +package cache_test + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + "testing/fstest" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/cache" +) + +const ( + package1 = `{ + "schema": "olm.package", + "name": "fake1" + }` + + bundle1 = `{ + "schema": "olm.bundle", + "name": "fake1.v1.0.0", + "package": "fake1", + "image": "fake-image", + "properties": [ + { + "type": "olm.package", + "value": {"packageName":"fake1","version":"1.0.0"} + } + ] + }` + + stableChannel = `{ + "schema": "olm.channel", + "name": "stable", + "package": "fake1", + "entries": [ + { + "name": "fake1.v1.0.0" + } + ] + }` +) + +func defaultContent() io.Reader { + return strings.NewReader(package1 + bundle1 + stableChannel) +} + +func defaultFS() fstest.MapFS { + return fstest.MapFS{ + "fake1/olm.package/fake1.json": &fstest.MapFile{Data: []byte(package1)}, + "fake1/olm.bundle/fake1.v1.0.0.json": &fstest.MapFile{Data: []byte(bundle1)}, + "fake1/olm.channel/stable.json": &fstest.MapFile{Data: []byte(stableChannel)}, + } +} + +func TestFilesystemCachePutAndGet(t *testing.T) { + const ( + catalogName = "test-catalog" + resolvedRef1 = "fake/catalog@sha256:fakesha1" + resolvedRef2 = "fake/catalog@sha256:fakesha2" + ) + + cacheDir := t.TempDir() + c := cache.NewFilesystemCache(cacheDir) + + catalogCachePath := filepath.Join(cacheDir, catalogName) + require.NoDirExists(t, catalogCachePath) + + t.Log("Get empty v1 cache") + actualFSGet, err := c.Get(catalogName, resolvedRef1) + require.NoError(t, err) + assert.Nil(t, actualFSGet) + + t.Log("Put v1 content into cache") + actualFSPut, err := c.Put(catalogName, resolvedRef1, defaultContent(), nil) + require.NoError(t, err) + require.NotNil(t, actualFSPut) + require.NoError(t, equalFilesystems(defaultFS(), actualFSPut)) + + t.Log("Get v1 content from cache") + actualFSGet, err = c.Get(catalogName, resolvedRef1) + require.NoError(t, err) + require.NotNil(t, actualFSGet) + assert.NoError(t, equalFilesystems(defaultFS(), actualFSPut)) + assert.NoError(t, equalFilesystems(actualFSPut, actualFSGet)) + + t.Log("Put v1 error into cache") + actualFSPut, err = c.Put(catalogName, resolvedRef1, nil, errors.New("fake v1 put error")) + // Errors for an existing resolvedRef should override previously successfully populated cache + assert.Equal(t, err, errors.New("fake v1 put error")) + assert.Nil(t, actualFSPut) + + t.Log("Put v2 error into cache") + actualFSPut, err = c.Put(catalogName, resolvedRef2, nil, errors.New("fake v2 put error")) + assert.Equal(t, errors.New("fake v2 put error"), err) + assert.Nil(t, actualFSPut) + + t.Log("Get v2 error from cache") + actualFSGet, err = c.Get(catalogName, resolvedRef2) + assert.Equal(t, errors.New("fake v2 put error"), err) + assert.Nil(t, actualFSGet) + + t.Log("Put v2 content into cache") + actualFSPut, err = c.Put(catalogName, resolvedRef2, defaultContent(), nil) + require.NoError(t, err) + require.NotNil(t, actualFSPut) + require.NoError(t, equalFilesystems(defaultFS(), actualFSPut)) + + t.Log("Get v2 content from cache") + actualFSGet, err = c.Get(catalogName, resolvedRef2) + require.NoError(t, err) + require.NotNil(t, actualFSGet) + assert.NoError(t, equalFilesystems(defaultFS(), actualFSPut)) + assert.NoError(t, equalFilesystems(actualFSPut, actualFSGet)) + + t.Log("Get empty v1 cache") + // Cache should be empty and no error because + // Put with a new version overrides the old version + actualFSGet, err = c.Get(catalogName, resolvedRef1) + require.NoError(t, err) + assert.Nil(t, actualFSGet) +} + +func TestFilesystemCacheRemove(t *testing.T) { + catalogName := "test-catalog" + resolvedRef := "fake/catalog@sha256:fakesha" + + cacheDir := t.TempDir() + c := cache.NewFilesystemCache(cacheDir) + + catalogCachePath := filepath.Join(cacheDir, catalogName) + + t.Log("Remove cache before it exists") + require.NoDirExists(t, catalogCachePath) + err := c.Remove(catalogName) + require.NoError(t, err) + assert.NoDirExists(t, catalogCachePath) + + t.Log("Fetch contents to populate cache") + _, err = c.Put(catalogName, resolvedRef, defaultContent(), nil) + require.NoError(t, err) + require.DirExists(t, catalogCachePath) + + t.Log("Temporary change permissions to the cache dir to cause error") + require.NoError(t, os.Chmod(catalogCachePath, 0000)) + + t.Log("Remove cache causes an error") + err = c.Remove(catalogName) + require.ErrorContains(t, err, "error removing cache directory") + require.DirExists(t, catalogCachePath) + + t.Log("Restore directory permissions for successful removal") + require.NoError(t, os.Chmod(catalogCachePath, 0777)) + + t.Log("Remove cache") + err = c.Remove(catalogName) + require.NoError(t, err) + assert.NoDirExists(t, catalogCachePath) +} + +func equalFilesystems(expected, actual fs.FS) error { + normalizeJSON := func(data []byte) []byte { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return data + } + norm, err := json.Marshal(v) + if err != nil { + return data + } + return norm + } + compare := func(expected, actual fs.FS, path string) error { + expectedData, expectedErr := fs.ReadFile(expected, path) + actualData, actualErr := fs.ReadFile(actual, path) + + switch { + case expectedErr == nil && actualErr != nil: + return fmt.Errorf("path %q: read error in actual FS: %v", path, actualErr) + case expectedErr != nil && actualErr == nil: + return fmt.Errorf("path %q: read error in expected FS: %v", path, expectedErr) + case expectedErr != nil && actualErr != nil && expectedErr.Error() != actualErr.Error(): + return fmt.Errorf("path %q: different read errors: expected: %v, actual: %v", path, expectedErr, actualErr) + } + + if filepath.Ext(path) == ".json" { + expectedData = normalizeJSON(expectedData) + actualData = normalizeJSON(actualData) + } + + if !bytes.Equal(expectedData, actualData) { + return fmt.Errorf("path %q: file contents do not match: %s", path, cmp.Diff(string(expectedData), string(actualData))) + } + return nil + } + + paths := sets.New[string]() + for _, fsys := range []fs.FS{expected, actual} { + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + paths.Insert(path) + return nil + }); err != nil { + return err + } + } + + var cmpErrs []error + for _, path := range sets.List(paths) { + if err := compare(expected, actual, path); err != nil { + cmpErrs = append(cmpErrs, err) + } + } + return errors.Join(cmpErrs...) +} diff --git a/internal/operator-controller/catalogmetadata/client/client.go b/internal/operator-controller/catalogmetadata/client/client.go new file mode 100644 index 0000000000..0d08c40ef5 --- /dev/null +++ b/internal/operator-controller/catalogmetadata/client/client.go @@ -0,0 +1,165 @@ +package client + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "net/url" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + httputil "github.com/operator-framework/operator-controller/internal/shared/util/http" +) + +const ( + clusterCatalogV1ApiURL = "api/v1/all" +) + +type Cache interface { + // Get returns cache for a specified catalog name and version (resolvedRef). + // + // Method behaviour is as follows: + // - If cache exists, it returns a non-nil fs.FS and nil error + // - If cache doesn't exist, it returns nil fs.FS and nil error + // - If there was an error during cache population, + // it returns nil fs.FS and the error from the cache population. + // In other words - cache population errors are also cached. + Get(catalogName, resolvedRef string) (fs.FS, error) + + // Put writes content from source or from errToCache in the cache backend + // for a specified catalog name and version (resolvedRef). + // + // Method behaviour is as follows: + // - If successfully populated cache for catalogName and resolvedRef exists, + // errToCache is ignored and existing cache returned with nil error + // - If existing cache for catalogName and resolvedRef exists but + // is populated with an error, update the cache with either + // new content from source or errToCache. + // - If cache doesn't exist, populate it with either new content + // from source or errToCache. + Put(catalogName, resolvedRef string, source io.Reader, errToCache error) (fs.FS, error) +} + +func New(cache Cache, httpClient func() (*http.Client, error)) *Client { + return &Client{ + cache: cache, + httpClient: httpClient, + } +} + +// Client is reading catalog metadata +type Client struct { + cache Cache + httpClient func() (*http.Client, error) +} + +func (c *Client) GetPackage(ctx context.Context, catalog *ocv1.ClusterCatalog, pkgName string) (*declcfg.DeclarativeConfig, error) { + if err := validateCatalog(catalog); err != nil { + return nil, err + } + + catalogFsys, err := c.cache.Get(catalog.Name, catalog.Status.ResolvedSource.Image.Ref) + if err != nil { + return nil, fmt.Errorf("error retrieving cache for catalog %q: %v", catalog.Name, err) + } + if catalogFsys == nil { + return nil, fmt.Errorf("cache for catalog %q not found", catalog.Name) + } + + pkgFsys, err := fs.Sub(catalogFsys, pkgName) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, fmt.Errorf("error getting package %q: %v", pkgName, err) + } + return &declcfg.DeclarativeConfig{}, nil + } + + pkgFBC, err := declcfg.LoadFS(ctx, pkgFsys) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, fmt.Errorf("error loading package %q: %v", pkgName, err) + } + return &declcfg.DeclarativeConfig{}, nil + } + return pkgFBC, nil +} + +func (c *Client) PopulateCache(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) { + if err := validateCatalog(catalog); err != nil { + return nil, err + } + + resp, err := c.doRequest(ctx, catalog) + if err != nil { + // Any errors from the http request we want to cache + // so later on cache get they can be bubbled up to the user. + return c.cache.Put(catalog.Name, catalog.Status.ResolvedSource.Image.Ref, nil, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + errToCache := fmt.Errorf("error: received unexpected response status code %d", resp.StatusCode) + return c.cache.Put(catalog.Name, catalog.Status.ResolvedSource.Image.Ref, nil, errToCache) + } + + return c.cache.Put(catalog.Name, catalog.Status.ResolvedSource.Image.Ref, resp.Body, nil) +} + +func (c *Client) doRequest(ctx context.Context, catalog *ocv1.ClusterCatalog) (*http.Response, error) { + if catalog.Status.URLs == nil { + return nil, fmt.Errorf("error: catalog %q has a nil status.urls value", catalog.Name) + } + + catalogdURL, err := url.JoinPath(catalog.Status.URLs.Base, clusterCatalogV1ApiURL) + if err != nil { + return nil, fmt.Errorf("error forming catalogd API endpoint: %v", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalogdURL, nil) + if err != nil { + return nil, fmt.Errorf("error forming request: %v", err) + } + + client, err := c.httpClient() + if err != nil { + return nil, fmt.Errorf("error getting HTTP client: %v", err) + } + + resp, err := client.Do(req) + if err != nil { + _ = httputil.LogCertificateVerificationError(err, ctrl.Log.WithName("catalog-client")) + return nil, fmt.Errorf("error performing request: %v", err) + } + + return resp, nil +} + +func validateCatalog(catalog *ocv1.ClusterCatalog) error { + if catalog == nil { + return fmt.Errorf("error: provided catalog must be non-nil") + } + + // if the catalog is not being served, report an error. This ensures that our + // reconciles are deterministic and wait for all desired catalogs to be ready. + if !meta.IsStatusConditionPresentAndEqual(catalog.Status.Conditions, ocv1.TypeServing, metav1.ConditionTrue) { + return fmt.Errorf("catalog %q is not being served", catalog.Name) + } + + if catalog.Status.ResolvedSource == nil { + return fmt.Errorf("error: catalog %q has a nil status.resolvedSource value", catalog.Name) + } + + if catalog.Status.ResolvedSource.Image == nil { + return fmt.Errorf("error: catalog %q has a nil status.resolvedSource.image value", catalog.Name) + } + + return nil +} diff --git a/internal/operator-controller/catalogmetadata/client/client_test.go b/internal/operator-controller/catalogmetadata/client/client_test.go new file mode 100644 index 0000000000..00a226873e --- /dev/null +++ b/internal/operator-controller/catalogmetadata/client/client_test.go @@ -0,0 +1,295 @@ +package client_test + +import ( + "context" + "errors" + "io" + "io/fs" + "net/http" + "strings" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + catalogClient "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/client" +) + +func defaultCatalog() *ocv1.ClusterCatalog { + return &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{Name: "catalog-1"}, + Status: ocv1.ClusterCatalogStatus{ + Conditions: []metav1.Condition{{Type: ocv1.TypeServing, Status: metav1.ConditionTrue}}, + ResolvedSource: &ocv1.ResolvedCatalogSource{Image: &ocv1.ResolvedImageSource{ + Ref: "fake/catalog@sha256:fakesha", + }}, + URLs: &ocv1.ClusterCatalogURLs{ + Base: "https://fake-url.svc.local/catalogs/catalog-1", + }, + }, + } +} + +func TestClientGetPackage(t *testing.T) { + testFS := fstest.MapFS{ + "pkg-present/olm.package/pkg-present.json": &fstest.MapFile{Data: []byte(`{"schema": "olm.package","name": "pkg-present"}`)}, + } + + type testCase struct { + name string + catalog func() *ocv1.ClusterCatalog + pkgName string + cache catalogClient.Cache + assert func(*testing.T, *declcfg.DeclarativeConfig, error) + } + for _, tc := range []testCase{ + { + name: "not served", + catalog: func() *ocv1.ClusterCatalog { + return &ocv1.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: "catalog-1"}} + }, + assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { + assert.ErrorContains(t, err, `catalog "catalog-1" is not being served`) + }, + }, + { + name: "served, cache returns error", + catalog: defaultCatalog, + cache: &fakeCache{getErr: errors.New("fetch error")}, + assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { + assert.ErrorContains(t, err, `error retrieving cache for catalog "catalog-1"`) + }, + }, + { + name: "served, invalid package path", + catalog: defaultCatalog, + cache: &fakeCache{getFS: testFS}, + pkgName: "/", + assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) { + assert.ErrorContains(t, err, `error getting package "/"`) + }, + }, + { + name: "served, package missing", + catalog: defaultCatalog, + pkgName: "pkg-missing", + cache: &fakeCache{getFS: testFS}, + assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { + require.NoError(t, err) + assert.Equal(t, &declcfg.DeclarativeConfig{}, fbc) + }, + }, + { + name: "served, invalid package present", + catalog: defaultCatalog, + pkgName: "invalid-pkg-present", + cache: &fakeCache{getFS: fstest.MapFS{ + "invalid-pkg-present/olm.package/invalid-pkg-present.json": &fstest.MapFile{Data: []byte(`{"schema": "olm.package","name": 12345}`)}, + }}, + assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { + require.ErrorContains(t, err, `error loading package "invalid-pkg-present"`) + assert.Nil(t, fbc) + }, + }, + { + name: "served, package present", + catalog: defaultCatalog, + pkgName: "pkg-present", + cache: &fakeCache{getFS: testFS}, + assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { + require.NoError(t, err) + assert.Equal(t, &declcfg.DeclarativeConfig{Packages: []declcfg.Package{{Schema: declcfg.SchemaPackage, Name: "pkg-present"}}}, fbc) + }, + }, + { + name: "cache unpopulated", + catalog: defaultCatalog, + pkgName: "pkg-present", + cache: &fakeCache{putFunc: func(source string, errToCache error) (fs.FS, error) { + return testFS, nil + }}, + assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) { + assert.ErrorContains(t, err, `cache for catalog "catalog-1" not found`) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + c := catalogClient.New(tc.cache, func() (*http.Client, error) { + return &http.Client{ + // This is to prevent actual network calls + Transport: &fakeTripper{resp: &http.Response{ + StatusCode: http.StatusOK, + Body: http.NoBody, + }}, + }, nil + }) + fbc, err := c.GetPackage(ctx, tc.catalog(), tc.pkgName) + tc.assert(t, fbc, err) + }) + } +} + +func TestClientPopulateCache(t *testing.T) { + testFS := fstest.MapFS{ + "pkg-present/olm.package/pkg-present.json": &fstest.MapFile{Data: []byte(`{"schema": "olm.package","name": "pkg-present"}`)}, + } + + type testCase struct { + name string + catalog func() *ocv1.ClusterCatalog + httpClient func() (*http.Client, error) + putFuncConstructor func(t *testing.T) func(source string, errToCache error) (fs.FS, error) + assert func(t *testing.T, fs fs.FS, err error) + } + for _, tt := range []testCase{ + { + name: "cache unpopulated, successful http request", + catalog: defaultCatalog, + httpClient: func() (*http.Client, error) { + return &http.Client{ + // This is to prevent actual network calls + Transport: &fakeTripper{resp: &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("fake-success-response-body")), + }}, + }, nil + }, + assert: func(t *testing.T, fs fs.FS, err error) { + require.NoError(t, err) + assert.Equal(t, testFS, fs) + }, + putFuncConstructor: func(t *testing.T) func(source string, errToCache error) (fs.FS, error) { + return func(source string, errToCache error) (fs.FS, error) { + assert.Equal(t, "fake-success-response-body", source) + assert.NoError(t, errToCache) + return testFS, errToCache + } + }, + }, + { + name: "not served", + catalog: func() *ocv1.ClusterCatalog { + return &ocv1.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: "catalog-1"}} + }, + assert: func(t *testing.T, fs fs.FS, err error) { + assert.Nil(t, fs) + assert.ErrorContains(t, err, `catalog "catalog-1" is not being served`) + }, + }, + { + name: "cache unpopulated, error on getting a http client", + catalog: defaultCatalog, + httpClient: func() (*http.Client, error) { + return nil, errors.New("fake error getting a http client") + }, + putFuncConstructor: func(t *testing.T) func(source string, errToCache error) (fs.FS, error) { + return func(source string, errToCache error) (fs.FS, error) { + assert.Empty(t, source) + assert.Error(t, errToCache) + return nil, errToCache + } + }, + assert: func(t *testing.T, fs fs.FS, err error) { + assert.Nil(t, fs) + assert.ErrorContains(t, err, "error getting HTTP client") + }, + }, + { + name: "cache unpopulated, error on http request", + catalog: defaultCatalog, + httpClient: func() (*http.Client, error) { + return &http.Client{ + // This is to prevent actual network calls + Transport: &fakeTripper{err: errors.New("fake error on a http request")}, + }, nil + }, + putFuncConstructor: func(t *testing.T) func(source string, errToCache error) (fs.FS, error) { + return func(source string, errToCache error) (fs.FS, error) { + assert.Empty(t, source) + assert.Error(t, errToCache) + return nil, errToCache + } + }, + assert: func(t *testing.T, fs fs.FS, err error) { + assert.Nil(t, fs) + assert.ErrorContains(t, err, "error performing request") + }, + }, + { + name: "cache unpopulated, unexpected http status", + catalog: defaultCatalog, + httpClient: func() (*http.Client, error) { + return &http.Client{ + // This is to prevent actual network calls + Transport: &fakeTripper{resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(strings.NewReader("fake-unexpected-code-response-body")), + }}, + }, nil + }, + putFuncConstructor: func(t *testing.T) func(source string, errToCache error) (fs.FS, error) { + return func(source string, errToCache error) (fs.FS, error) { + assert.Empty(t, source) + assert.Error(t, errToCache) + return nil, errToCache + } + }, + assert: func(t *testing.T, fs fs.FS, err error) { + assert.Nil(t, fs) + assert.ErrorContains(t, err, "received unexpected response status code 500") + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + cache := &fakeCache{} + if tt.putFuncConstructor != nil { + cache.putFunc = tt.putFuncConstructor(t) + } + + c := catalogClient.New(cache, tt.httpClient) + fs, err := c.PopulateCache(ctx, tt.catalog()) + tt.assert(t, fs, err) + }) + } +} + +type fakeCache struct { + getFS fs.FS + getErr error + + putFunc func(source string, errToCache error) (fs.FS, error) +} + +func (c *fakeCache) Get(catalogName, resolvedRef string) (fs.FS, error) { + return c.getFS, c.getErr +} + +func (c *fakeCache) Put(catalogName, resolvedRef string, source io.Reader, errToCache error) (fs.FS, error) { + if c.putFunc != nil { + buf := new(strings.Builder) + if source != nil { + io.Copy(buf, source) // nolint:errcheck + } + return c.putFunc(buf.String(), errToCache) + } + + return nil, errors.New("unexpected error") +} + +type fakeTripper struct { + resp *http.Response + err error +} + +func (ft *fakeTripper) RoundTrip(*http.Request) (*http.Response, error) { + return ft.resp, ft.err +} diff --git a/internal/catalogmetadata/compare/compare.go b/internal/operator-controller/catalogmetadata/compare/compare.go similarity index 93% rename from internal/catalogmetadata/compare/compare.go rename to internal/operator-controller/catalogmetadata/compare/compare.go index 1b76d0cdef..4c52eda4e1 100644 --- a/internal/catalogmetadata/compare/compare.go +++ b/internal/operator-controller/catalogmetadata/compare/compare.go @@ -5,7 +5,7 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-controller/internal/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" ) // ByVersion is a sort "less" function that orders bundles diff --git a/internal/catalogmetadata/compare/compare_test.go b/internal/operator-controller/catalogmetadata/compare/compare_test.go similarity index 95% rename from internal/catalogmetadata/compare/compare_test.go rename to internal/operator-controller/catalogmetadata/compare/compare_test.go index 095c879c11..c5d1735dc2 100644 --- a/internal/catalogmetadata/compare/compare_test.go +++ b/internal/operator-controller/catalogmetadata/compare/compare_test.go @@ -10,7 +10,7 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/compare" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/compare" ) func TestByVersion(t *testing.T) { diff --git a/internal/catalogmetadata/filter/bundle_predicates.go b/internal/operator-controller/catalogmetadata/filter/bundle_predicates.go similarity index 77% rename from internal/catalogmetadata/filter/bundle_predicates.go rename to internal/operator-controller/catalogmetadata/filter/bundle_predicates.go index 98e3ab4cda..ecea3783b7 100644 --- a/internal/catalogmetadata/filter/bundle_predicates.go +++ b/internal/operator-controller/catalogmetadata/filter/bundle_predicates.go @@ -5,10 +5,11 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" - "github.com/operator-framework/operator-controller/internal/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" + "github.com/operator-framework/operator-controller/internal/shared/util/filter" ) -func InMastermindsSemverRange(semverRange *mmsemver.Constraints) Predicate[declcfg.Bundle] { +func InMastermindsSemverRange(semverRange *mmsemver.Constraints) filter.Predicate[declcfg.Bundle] { return func(b declcfg.Bundle) bool { bVersion, err := bundleutil.GetVersion(b) if err != nil { @@ -26,7 +27,7 @@ func InMastermindsSemverRange(semverRange *mmsemver.Constraints) Predicate[declc } } -func InAnyChannel(channels ...declcfg.Channel) Predicate[declcfg.Bundle] { +func InAnyChannel(channels ...declcfg.Channel) filter.Predicate[declcfg.Bundle] { return func(bundle declcfg.Bundle) bool { for _, ch := range channels { for _, entry := range ch.Entries { diff --git a/internal/catalogmetadata/filter/bundle_predicates_test.go b/internal/operator-controller/catalogmetadata/filter/bundle_predicates_test.go similarity index 90% rename from internal/catalogmetadata/filter/bundle_predicates_test.go rename to internal/operator-controller/catalogmetadata/filter/bundle_predicates_test.go index dd6abe6d7c..da47b961f7 100644 --- a/internal/catalogmetadata/filter/bundle_predicates_test.go +++ b/internal/operator-controller/catalogmetadata/filter/bundle_predicates_test.go @@ -6,11 +6,12 @@ import ( mmsemver "github.com/Masterminds/semver/v3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/filter" ) func TestInMastermindsSemverRange(t *testing.T) { @@ -40,7 +41,7 @@ func TestInMastermindsSemverRange(t *testing.T) { } vRange, err := mmsemver.NewConstraint(">=1.0.0") - assert.NoError(t, err) + require.NoError(t, err) f := filter.InMastermindsSemverRange(vRange) diff --git a/internal/operator-controller/catalogmetadata/filter/successors.go b/internal/operator-controller/catalogmetadata/filter/successors.go new file mode 100644 index 0000000000..c4abb32589 --- /dev/null +++ b/internal/operator-controller/catalogmetadata/filter/successors.go @@ -0,0 +1,77 @@ +package filter + +import ( + "fmt" + + mmsemver "github.com/Masterminds/semver/v3" + bsemver "github.com/blang/semver/v4" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/shared/util/filter" +) + +func SuccessorsOf(installedBundle ocv1.BundleMetadata, channels ...declcfg.Channel) (filter.Predicate[declcfg.Bundle], error) { + installedBundleVersion, err := mmsemver.NewVersion(installedBundle.Version) + if err != nil { + return nil, fmt.Errorf("parsing installed bundle %q version %q: %w", installedBundle.Name, installedBundle.Version, err) + } + + installedVersionConstraint, err := mmsemver.NewConstraint(installedBundleVersion.String()) + if err != nil { + return nil, fmt.Errorf("parsing installed version constraint %q: %w", installedBundleVersion.String(), err) + } + + successorsPredicate, err := legacySuccessor(installedBundle, channels...) + if err != nil { + return nil, fmt.Errorf("getting successorsPredicate: %w", err) + } + + // We need either successors or current version (no upgrade) + return filter.Or( + successorsPredicate, + InMastermindsSemverRange(installedVersionConstraint), + ), nil +} + +func legacySuccessor(installedBundle ocv1.BundleMetadata, channels ...declcfg.Channel) (filter.Predicate[declcfg.Bundle], error) { + installedBundleVersion, err := bsemver.Parse(installedBundle.Version) + if err != nil { + return nil, fmt.Errorf("error parsing installed bundle version: %w", err) + } + + isSuccessor := func(candidateBundleEntry declcfg.ChannelEntry) bool { + if candidateBundleEntry.Replaces == installedBundle.Name { + return true + } + for _, skip := range candidateBundleEntry.Skips { + if skip == installedBundle.Name { + return true + } + } + if candidateBundleEntry.SkipRange != "" { + // There are differences between how "github.com/blang/semver/v4" and "github.com/Masterminds/semver/v3" + // handle version ranges. OLM v0 used blang and there might still be registry+v1 bundles that rely + // on those specific differences. Because OLM v1 supports registry+v1 bundles, + // blang needs to be kept alongside any other semver lib for range handling. + // see: https://github.com/operator-framework/operator-controller/pull/1565#issuecomment-2586455768 + skipRange, err := bsemver.ParseRange(candidateBundleEntry.SkipRange) + if err == nil && skipRange(installedBundleVersion) { + return true + } + } + return false + } + + return func(candidateBundle declcfg.Bundle) bool { + for _, ch := range channels { + for _, chEntry := range ch.Entries { + if candidateBundle.Name == chEntry.Name && isSuccessor(chEntry) { + return true + } + } + } + return false + }, nil +} diff --git a/internal/operator-controller/catalogmetadata/filter/successors_test.go b/internal/operator-controller/catalogmetadata/filter/successors_test.go new file mode 100644 index 0000000000..0d3fb45d2b --- /dev/null +++ b/internal/operator-controller/catalogmetadata/filter/successors_test.go @@ -0,0 +1,217 @@ +package filter + +import ( + "slices" + "testing" + + bsemver "github.com/blang/semver/v4" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/compare" + "github.com/operator-framework/operator-controller/internal/shared/util/filter" +) + +func TestSuccessorsPredicate(t *testing.T) { + const testPackageName = "test-package" + channelSet := map[string]declcfg.Channel{ + testPackageName: { + Name: "stable", + Package: testPackageName, + Entries: []declcfg.ChannelEntry{ + { + Name: "test-package.v2.0.0", + }, + { + Name: "test-package.v2.1.0", + Replaces: "test-package.v2.0.0", + }, + { + Name: "test-package.v2.2.0", + Replaces: "test-package.v2.1.0", + }, + { + Name: "test-package.v2.2.1", + }, + { + Name: "test-package.v2.3.0", + Replaces: "test-package.v2.2.0", + Skips: []string{ + "test-package.v2.2.1", + }, + }, + { + Name: "test-package.v2.4.0", + SkipRange: ">=2.3.0 <2.4.0", + }, + }, + }, + } + + bundleSet := map[string]declcfg.Bundle{ + "test-package.v2.0.0": { + Name: "test-package.v2.0.0", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.0.0", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.0.0"), + }, + }, + "test-package.v2.1.0": { + Name: "test-package.v2.1.0", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.1.0", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.1.0"), + }, + }, + "test-package.v2.2.0": { + Name: "test-package.v2.2.0", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.2.0", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.2.0"), + }, + }, + "test-package.v2.2.1": { + Name: "test-package.v2.2.1", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.2.1", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.2.1"), + }, + }, + "test-package.v2.3.0": { + Name: "test-package.v2.3.0", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.3.0", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.3.0"), + }, + }, + "test-package.v2.4.0": { + Name: "test-package.v2.4.0", + Package: testPackageName, + Image: "registry.io/repo/test-package@v2.4.0", + Properties: []property.Property{ + property.MustBuildPackage(testPackageName, "2.4.0"), + }, + }, + } + + for _, tt := range []struct { + name string + installedBundle ocv1.BundleMetadata + expectedResult []declcfg.Bundle + }{ + { + name: "respect replaces directive from catalog", + installedBundle: bundleutil.MetadataFor("test-package.v2.0.0", bsemver.MustParse("2.0.0")), + expectedResult: []declcfg.Bundle{ + // Must only have two bundle: + // - the one which replaces the current version + // - the current version (to allow to stay on the current version) + bundleSet["test-package.v2.1.0"], + bundleSet["test-package.v2.0.0"], + }, + }, + { + name: "respect skips directive from catalog", + installedBundle: bundleutil.MetadataFor("test-package.v2.2.1", bsemver.MustParse("2.2.1")), + expectedResult: []declcfg.Bundle{ + // Must only have two bundle: + // - the one which skips the current version + // - the current version (to allow to stay on the current version) + bundleSet["test-package.v2.3.0"], + bundleSet["test-package.v2.2.1"], + }, + }, + { + name: "respect skipRange directive from catalog", + installedBundle: bundleutil.MetadataFor("test-package.v2.3.0", bsemver.MustParse("2.3.0")), + expectedResult: []declcfg.Bundle{ + // Must only have two bundle: + // - the one which is skipRanges the current version + // - the current version (to allow to stay on the current version) + bundleSet["test-package.v2.4.0"], + bundleSet["test-package.v2.3.0"], + }, + }, + { + name: "installed bundle not found", + installedBundle: ocv1.BundleMetadata{ + Name: "test-package.v9.0.0", + Version: "9.0.0", + }, + expectedResult: []declcfg.Bundle{}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + successors, err := SuccessorsOf(tt.installedBundle, channelSet[testPackageName]) + require.NoError(t, err) + + allBundles := make([]declcfg.Bundle, 0, len(bundleSet)) + for _, bundle := range bundleSet { + allBundles = append(allBundles, bundle) + } + result := filter.InPlace(allBundles, successors) + + // sort before comparison for stable order + slices.SortFunc(result, compare.ByVersion) + + gocmpopts := []cmp.Option{ + cmpopts.IgnoreUnexported(declcfg.Bundle{}), + } + require.Empty(t, cmp.Diff(result, tt.expectedResult, gocmpopts...)) + }) + } +} + +func TestLegacySuccessor(t *testing.T) { + fakeChannel := declcfg.Channel{ + Entries: []declcfg.ChannelEntry{ + { + Name: "package1.v0.0.2", + Replaces: "package1.v0.0.1", + }, + { + Name: "package1.v0.0.3", + Replaces: "package1.v0.0.2", + }, + { + Name: "package1.v0.0.4", + Skips: []string{"package1.v0.0.1"}, + }, + { + Name: "package1.v0.0.5", + SkipRange: "<=0.0.1", + }, + }, + } + installedBundle := ocv1.BundleMetadata{ + Name: "package1.v0.0.1", + Version: "0.0.1", + } + + b2 := declcfg.Bundle{Name: "package1.v0.0.2"} + b3 := declcfg.Bundle{Name: "package1.v0.0.3"} + b4 := declcfg.Bundle{Name: "package1.v0.0.4"} + b5 := declcfg.Bundle{Name: "package1.v0.0.5"} + emptyBundle := declcfg.Bundle{} + + f, err := legacySuccessor(installedBundle, fakeChannel) + require.NoError(t, err) + + assert.True(t, f(b2)) + assert.False(t, f(b3)) + assert.True(t, f(b4)) + assert.True(t, f(b5)) + assert.False(t, f(emptyBundle)) +} diff --git a/internal/conditionsets/conditionsets.go b/internal/operator-controller/conditionsets/conditionsets.go similarity index 57% rename from internal/conditionsets/conditionsets.go rename to internal/operator-controller/conditionsets/conditionsets.go index fa4087148d..0d63e1abb2 100644 --- a/internal/conditionsets/conditionsets.go +++ b/internal/operator-controller/conditionsets/conditionsets.go @@ -16,9 +16,29 @@ limitations under the License. package conditionsets +import ( + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + // ConditionTypes is the full set of ClusterExtension condition Types. // ConditionReasons is the full set of ClusterExtension condition Reasons. // -// NOTE: These are populated by init() in api/v1alpha1/clusterextension_types.go -var ConditionTypes []string -var ConditionReasons []string +// NOTE: unit tests in clusterextension_types_test will enforce completeness. +var ConditionTypes = []string{ + ocv1.TypeInstalled, + ocv1.TypeDeprecated, + ocv1.TypePackageDeprecated, + ocv1.TypeChannelDeprecated, + ocv1.TypeBundleDeprecated, + ocv1.TypeProgressing, +} + +var ConditionReasons = []string{ + ocv1.ReasonSucceeded, + ocv1.ReasonDeprecated, + ocv1.ReasonFailed, + ocv1.ReasonBlocked, + ocv1.ReasonRetrying, + ocv1.ReasonAbsent, + ocv1.ReasonRolloutInProgress, +} diff --git a/internal/contentmanager/cache/cache.go b/internal/operator-controller/contentmanager/cache/cache.go similarity index 100% rename from internal/contentmanager/cache/cache.go rename to internal/operator-controller/contentmanager/cache/cache.go diff --git a/internal/contentmanager/cache/cache_test.go b/internal/operator-controller/contentmanager/cache/cache_test.go similarity index 93% rename from internal/contentmanager/cache/cache_test.go rename to internal/operator-controller/contentmanager/cache/cache_test.go index edf5c0e7a3..da44551681 100644 --- a/internal/contentmanager/cache/cache_test.go +++ b/internal/operator-controller/contentmanager/cache/cache_test.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) type mockWatcher struct { @@ -64,7 +64,7 @@ func TestCacheWatch(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -81,7 +81,7 @@ func TestCacheWatchInvalidGVK(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -94,7 +94,7 @@ func TestCacheWatchSourcererError(t *testing.T) { &mockSourcerer{ err: errors.New("error"), }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -109,7 +109,7 @@ func TestCacheWatchWatcherError(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -126,7 +126,7 @@ func TestCacheWatchSourceWaitForSyncError(t *testing.T) { err: errors.New("error"), }, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -142,7 +142,7 @@ func TestCacheWatchExistingSourceNotPanic(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -162,7 +162,7 @@ func TestCacheWatchRemovesStaleSources(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) @@ -186,7 +186,7 @@ func TestCacheWatchRemovingStaleSourcesError(t *testing.T) { &mockSourcerer{ source: &mockSource{}, }, - &ocv1alpha1.ClusterExtension{}, + &ocv1.ClusterExtension{}, time.Second, ) diff --git a/internal/contentmanager/contentmanager.go b/internal/operator-controller/contentmanager/contentmanager.go similarity index 89% rename from internal/contentmanager/contentmanager.go rename to internal/operator-controller/contentmanager/contentmanager.go index 13fc92f9ba..d488bdb535 100644 --- a/internal/contentmanager/contentmanager.go +++ b/internal/operator-controller/contentmanager/contentmanager.go @@ -14,9 +14,9 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/operator-framework/operator-controller/api/v1alpha1" - cmcache "github.com/operator-framework/operator-controller/internal/contentmanager/cache" - oclabels "github.com/operator-framework/operator-controller/internal/labels" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + cmcache "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache" + oclabels "github.com/operator-framework/operator-controller/internal/operator-controller/labels" ) // Manager is a utility to manage content caches belonging @@ -25,10 +25,10 @@ type Manager interface { // Get returns a managed content cache for the provided // ClusterExtension if one exists. If one does not exist, // a new Cache is created and returned - Get(context.Context, *v1alpha1.ClusterExtension) (cmcache.Cache, error) + Get(context.Context, *ocv1.ClusterExtension) (cmcache.Cache, error) // Delete will stop and remove a managed content cache // for the provided ClusterExtension if one exists. - Delete(*v1alpha1.ClusterExtension) error + Delete(*ocv1.ClusterExtension) error } type RestConfigMapper func(context.Context, client.Object, *rest.Config) (*rest.Config, error) @@ -84,7 +84,7 @@ func NewManager(rcm RestConfigMapper, cfg *rest.Config, mapper meta.RESTMapper, // Get returns a Cache for the provided ClusterExtension. // If a cache does not already exist, a new one will be created. // If a nil ClusterExtension is provided this function will panic. -func (i *managerImpl) Get(ctx context.Context, ce *v1alpha1.ClusterExtension) (cmcache.Cache, error) { +func (i *managerImpl) Get(ctx context.Context, ce *ocv1.ClusterExtension) (cmcache.Cache, error) { if ce == nil { panic("nil ClusterExtension provided") } @@ -107,7 +107,7 @@ func (i *managerImpl) Get(ctx context.Context, ce *v1alpha1.ClusterExtension) (c } tgtLabels := labels.Set{ - oclabels.OwnerKindKey: v1alpha1.ClusterExtensionKind, + oclabels.OwnerKindKey: ocv1.ClusterExtensionKind, oclabels.OwnerNameKey: ce.GetName(), } @@ -129,7 +129,7 @@ func (i *managerImpl) Get(ctx context.Context, ce *v1alpha1.ClusterExtension) (c } // Delete stops and removes the Cache for the provided ClusterExtension -func (i *managerImpl) Delete(ce *v1alpha1.ClusterExtension) error { +func (i *managerImpl) Delete(ce *ocv1.ClusterExtension) error { if ce == nil { panic("nil ClusterExtension provided") } diff --git a/internal/contentmanager/source/dynamicsource.go b/internal/operator-controller/contentmanager/source/dynamicsource.go similarity index 96% rename from internal/contentmanager/source/dynamicsource.go rename to internal/operator-controller/contentmanager/source/dynamicsource.go index 1f539871f0..9a4e6910db 100644 --- a/internal/contentmanager/source/dynamicsource.go +++ b/internal/operator-controller/contentmanager/source/dynamicsource.go @@ -18,7 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - source "github.com/operator-framework/operator-controller/internal/contentmanager/source/internal" + source "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/source/internal" ) type DynamicSourceConfig struct { @@ -120,7 +120,7 @@ func (dis *dynamicInformerSource) Start(ctx context.Context, q workqueue.TypedRa dis.cfg.OnPostSyncError(dis.informerCtx) } dis.informerCancel() - cgocache.DefaultWatchErrorHandler(r, err) + cgocache.DefaultWatchErrorHandler(dis.informerCtx, r, err) }) if err != nil { return fmt.Errorf("setting WatchErrorHandler: %w", err) @@ -139,7 +139,7 @@ func (dis *dynamicInformerSource) Start(ctx context.Context, q workqueue.TypedRa close(dis.syncedChan) }) - _ = wait.PollUntilContextCancel(dis.informerCtx, time.Second, true, func(_ context.Context) (bool, error) { + _ = wait.PollUntilContextCancel(dis.informerCtx, time.Millisecond*100, true, func(_ context.Context) (bool, error) { if sharedIndexInf.HasSynced() { syncOnce() return true, nil diff --git a/internal/contentmanager/source/dynamicsource_test.go b/internal/operator-controller/contentmanager/source/dynamicsource_test.go similarity index 91% rename from internal/contentmanager/source/dynamicsource_test.go rename to internal/operator-controller/contentmanager/source/dynamicsource_test.go index 61cbab8ca5..3a89a639e0 100644 --- a/internal/contentmanager/source/dynamicsource_test.go +++ b/internal/operator-controller/contentmanager/source/dynamicsource_test.go @@ -7,14 +7,14 @@ import ( "time" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic/dynamicinformer" dynamicfake "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) func TestDynamicInformerSourceCloseBeforeStartErrors(t *testing.T) { @@ -92,7 +92,7 @@ func TestDynamicInformerSourceStartAlreadyStarted(t *testing.T) { } func TestDynamicInformerSourceStart(t *testing.T) { - fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme()) + fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) infFact := dynamicinformer.NewDynamicSharedInformerFactory(fakeDynamicClient, time.Minute) dis := NewDynamicSource(DynamicSourceConfig{ DynamicInformerFactory: infFact, @@ -101,12 +101,16 @@ func TestDynamicInformerSourceStart(t *testing.T) { Version: "v1", Resource: "pods", }, - Owner: &v1alpha1.ClusterExtension{}, + Owner: &ocv1.ClusterExtension{}, Handler: handler.Funcs{}, Predicates: []predicate.Predicate{}, OnPostSyncError: func(ctx context.Context) {}, }) require.NoError(t, dis.Start(context.Background(), nil)) + + waitCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + require.NoError(t, dis.WaitForSync(waitCtx)) require.NoError(t, dis.Close()) } diff --git a/internal/contentmanager/source/internal/eventhandler.go b/internal/operator-controller/contentmanager/source/internal/eventhandler.go similarity index 82% rename from internal/contentmanager/source/internal/eventhandler.go rename to internal/operator-controller/contentmanager/source/internal/eventhandler.go index 54625af09a..dde5ed3d28 100644 --- a/internal/contentmanager/source/internal/eventhandler.go +++ b/internal/operator-controller/contentmanager/source/internal/eventhandler.go @@ -38,6 +38,7 @@ limitations under the License. import ( "context" + "errors" "fmt" cgocache "k8s.io/client-go/tools/cache" @@ -94,8 +95,11 @@ func (e *EventHandler[object, request]) OnAdd(obj interface{}) { if o, ok := obj.(object); ok { c.Object = o } else { - log.Error(nil, "OnAdd missing Object", - "object", obj, "type", fmt.Sprintf("%T", obj)) + log.Error(errors.New("failed to cast object"), + "OnAdd missing Object", + "expected_type", fmt.Sprintf("%T", c.Object), + "received_type", fmt.Sprintf("%T", obj), + "object", obj) return } @@ -118,8 +122,11 @@ func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj interface{}) { if o, ok := oldObj.(object); ok { u.ObjectOld = o } else { - log.Error(nil, "OnUpdate missing ObjectOld", - "object", oldObj, "type", fmt.Sprintf("%T", oldObj)) + log.Error(errors.New("failed to cast old object"), + "OnUpdate missing ObjectOld", + "object", oldObj, + "expected_type", fmt.Sprintf("%T", u.ObjectOld), + "received_type", fmt.Sprintf("%T", oldObj)) return } @@ -127,11 +134,15 @@ func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj interface{}) { if o, ok := newObj.(object); ok { u.ObjectNew = o } else { - log.Error(nil, "OnUpdate missing ObjectNew", - "object", newObj, "type", fmt.Sprintf("%T", newObj)) + log.Error(errors.New("failed to cast new object"), + "OnUpdate missing ObjectNew", + "object", newObj, + "expected_type", fmt.Sprintf("%T", u.ObjectNew), + "received_type", fmt.Sprintf("%T", newObj)) return } + // Run predicates before proceeding for _, p := range e.predicates { if !p.Update(u) { return @@ -148,18 +159,25 @@ func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj interface{}) { func (e *EventHandler[object, request]) OnDelete(obj interface{}) { d := event.TypedDeleteEvent[object]{} + // Handle tombstone events (cache.DeletedFinalStateUnknown) + if obj == nil { + log.Error(errors.New("received nil object"), + "OnDelete received a nil object, ignoring event") + return + } + // Deal with tombstone events by pulling the object out. Tombstone events wrap the object in a // DeleteFinalStateUnknown struct, so the object needs to be pulled out. // Copied from sample-controller // This should never happen if we aren't missing events, which we have concluded that we are not // and made decisions off of this belief. Maybe this shouldn't be here? - var ok bool - if _, ok = obj.(client.Object); !ok { + if _, ok := obj.(client.Object); !ok { // If the object doesn't have Metadata, assume it is a tombstone object of type DeletedFinalStateUnknown tombstone, ok := obj.(cgocache.DeletedFinalStateUnknown) if !ok { - log.Error(nil, "Error decoding objects. Expected cache.DeletedFinalStateUnknown", - "type", fmt.Sprintf("%T", obj), + log.Error(errors.New("unexpected object type"), + "Error decoding objects, expected cache.DeletedFinalStateUnknown", + "received_type", fmt.Sprintf("%T", obj), "object", obj) return } @@ -175,8 +193,11 @@ func (e *EventHandler[object, request]) OnDelete(obj interface{}) { if o, ok := obj.(object); ok { d.Object = o } else { - log.Error(nil, "OnDelete missing Object", - "object", obj, "type", fmt.Sprintf("%T", obj)) + log.Error(errors.New("failed to cast object"), + "OnDelete missing Object", + "expected_type", fmt.Sprintf("%T", d.Object), + "received_type", fmt.Sprintf("%T", obj), + "object", obj) return } diff --git a/internal/contentmanager/sourcerer.go b/internal/operator-controller/contentmanager/sourcerer.go similarity index 91% rename from internal/contentmanager/sourcerer.go rename to internal/operator-controller/contentmanager/sourcerer.go index a946cb7bad..050de87854 100644 --- a/internal/contentmanager/sourcerer.go +++ b/internal/operator-controller/contentmanager/sourcerer.go @@ -15,9 +15,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/contentmanager/cache" - "github.com/operator-framework/operator-controller/internal/contentmanager/source" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache" + "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/source" ) type dynamicSourcerer struct { @@ -68,7 +68,7 @@ func buildScheme(gvks ...schema.GroupVersionKind) (*runtime.Scheme, error) { // The ClusterExtension types must be added to the scheme since its // going to be used to establish watches that trigger reconciliation // of the owning ClusterExtension - if err := v1alpha1.AddToScheme(scheme); err != nil { + if err := ocv1.AddToScheme(scheme); err != nil { return nil, fmt.Errorf("adding operator controller APIs to scheme: %w", err) } diff --git a/internal/operator-controller/controllers/clustercatalog_controller.go b/internal/operator-controller/controllers/clustercatalog_controller.go new file mode 100644 index 0000000000..0654d83e7a --- /dev/null +++ b/internal/operator-controller/controllers/clustercatalog_controller.go @@ -0,0 +1,99 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + "io/fs" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +type CatalogCache interface { + Get(catalogName, resolvedRef string) (fs.FS, error) + Remove(catalogName string) error +} + +type CatalogCachePopulator interface { + PopulateCache(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) +} + +// ClusterCatalogReconciler reconciles a ClusterCatalog object +type ClusterCatalogReconciler struct { + client.Client + CatalogCache CatalogCache + CatalogCachePopulator CatalogCachePopulator +} + +func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx).WithName("cluster-catalog") + ctx = log.IntoContext(ctx, l) + + l.Info("reconcile starting") + defer l.Info("reconcile ending") + + existingCatalog := &ocv1.ClusterCatalog{} + err := r.Get(ctx, req.NamespacedName, existingCatalog) + if apierrors.IsNotFound(err) { + if err := r.CatalogCache.Remove(req.Name); err != nil { + return ctrl.Result{}, fmt.Errorf("error removing cache for catalog %q: %v", req.Name, err) + } + return ctrl.Result{}, nil + } + if err != nil { + return ctrl.Result{}, err + } + + if existingCatalog.Status.ResolvedSource == nil || + existingCatalog.Status.ResolvedSource.Image == nil || + existingCatalog.Status.ResolvedSource.Image.Ref == "" { + // Reference is not known yet - skip cache population with no error. + // Once the reference is resolved another reconcile cycle + // will be triggered and we will progress further. + return ctrl.Result{}, nil + } + + catalogFsys, err := r.CatalogCache.Get(existingCatalog.Name, existingCatalog.Status.ResolvedSource.Image.Ref) + if err != nil { + l.Info("retrying cache population: found previous error from catalog cache", "cacheErr", err) + } else if catalogFsys != nil { + // Cache already exists so we do not need to populate it + return ctrl.Result{}, nil + } + + if _, err = r.CatalogCachePopulator.PopulateCache(ctx, existingCatalog); err != nil { + return ctrl.Result{}, fmt.Errorf("error populating cache for catalog %q: %v", existingCatalog.Name, err) + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error { + _, err := ctrl.NewControllerManagedBy(mgr). + Named("controller-operator-clustercatalog-controller"). + For(&ocv1.ClusterCatalog{}). + Build(r) + + return err +} diff --git a/internal/operator-controller/controllers/clustercatalog_controller_test.go b/internal/operator-controller/controllers/clustercatalog_controller_test.go new file mode 100644 index 0000000000..aad0bb006b --- /dev/null +++ b/internal/operator-controller/controllers/clustercatalog_controller_test.go @@ -0,0 +1,228 @@ +package controllers_test + +import ( + "context" + "errors" + "io/fs" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" +) + +func TestClusterCatalogReconcilerFinalizers(t *testing.T) { + const fakeResolvedRef = "fake/catalog@sha256:fakesha1" + catalogKey := types.NamespacedName{Name: "test-catalog"} + + for _, tt := range []struct { + name string + catalog *ocv1.ClusterCatalog + catalogCache mockCatalogCache + catalogCachePopulator mockCatalogCachePopulator + wantGetCacheCalled bool + wantRemoveCacheCalled bool + wantPopulateCacheCalled bool + wantErr string + }{ + { + name: "catalog exists - cache unpopulated", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogKey.Name, + }, + Status: ocv1.ClusterCatalogStatus{ + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Image: &ocv1.ResolvedImageSource{ + Ref: fakeResolvedRef, + }, + }, + }, + }, + catalogCachePopulator: mockCatalogCachePopulator{ + populateCacheFunc: func(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) { + assert.Equal(t, catalogKey.Name, catalog.Name) + return nil, nil + }, + }, + wantGetCacheCalled: true, + wantPopulateCacheCalled: true, + }, + { + name: "catalog exists - cache already populated", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogKey.Name, + }, + Status: ocv1.ClusterCatalogStatus{ + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Image: &ocv1.ResolvedImageSource{ + Ref: fakeResolvedRef, + }, + }, + }, + }, + catalogCache: mockCatalogCache{ + getFunc: func(catalogName, resolvedRef string) (fs.FS, error) { + assert.Equal(t, catalogKey.Name, catalogName) + assert.Equal(t, fakeResolvedRef, resolvedRef) + // Just any non-nil fs.FS to simulate existence of cache + return fstest.MapFS{}, nil + }, + }, + wantGetCacheCalled: true, + }, + { + name: "catalog exists - catalog not yet resolved", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogKey.Name, + }, + }, + }, + { + name: "catalog exists - error on cache population", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogKey.Name, + }, + Status: ocv1.ClusterCatalogStatus{ + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Image: &ocv1.ResolvedImageSource{ + Ref: fakeResolvedRef, + }, + }, + }, + }, + catalogCachePopulator: mockCatalogCachePopulator{ + populateCacheFunc: func(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) { + assert.Equal(t, catalogKey.Name, catalog.Name) + return nil, errors.New("fake error from populate cache function") + }, + }, + wantGetCacheCalled: true, + wantPopulateCacheCalled: true, + wantErr: "error populating cache for catalog", + }, + { + name: "catalog exists - error on cache get", + catalog: &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: catalogKey.Name, + }, + Status: ocv1.ClusterCatalogStatus{ + ResolvedSource: &ocv1.ResolvedCatalogSource{ + Image: &ocv1.ResolvedImageSource{ + Ref: fakeResolvedRef, + }, + }, + }, + }, + catalogCache: mockCatalogCache{ + getFunc: func(catalogName, resolvedRef string) (fs.FS, error) { + assert.Equal(t, catalogKey.Name, catalogName) + assert.Equal(t, fakeResolvedRef, resolvedRef) + return nil, errors.New("fake error from cache get function") + }, + }, + wantGetCacheCalled: true, + wantPopulateCacheCalled: true, + }, + { + name: "catalog does not exist", + catalogCache: mockCatalogCache{ + removeFunc: func(catalogName string) error { + assert.Equal(t, catalogKey.Name, catalogName) + return nil + }, + }, + wantRemoveCacheCalled: true, + }, + { + name: "catalog does not exist - error on removal", + catalogCache: mockCatalogCache{ + removeFunc: func(catalogName string) error { + assert.Equal(t, catalogKey.Name, catalogName) + return errors.New("fake error from remove") + }, + }, + wantRemoveCacheCalled: true, + wantErr: "error removing cache for catalog", + }, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + clientBuilder := fake.NewClientBuilder().WithScheme(scheme.Scheme) + if tt.catalog != nil { + clientBuilder = clientBuilder.WithObjects(tt.catalog) + } + cl := clientBuilder.Build() + + reconciler := &controllers.ClusterCatalogReconciler{ + Client: cl, + CatalogCache: controllers.CatalogCache(&tt.catalogCache), + CatalogCachePopulator: controllers.CatalogCachePopulator(&tt.catalogCachePopulator), + } + + result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: catalogKey}) + if tt.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + require.Equal(t, ctrl.Result{}, result) + + assert.Equal(t, tt.wantRemoveCacheCalled, tt.catalogCache.removeFuncCalled) + assert.Equal(t, tt.wantGetCacheCalled, tt.catalogCache.getFuncCalled) + assert.Equal(t, tt.wantPopulateCacheCalled, tt.catalogCachePopulator.populateCacheCalled) + }) + } +} + +type mockCatalogCache struct { + removeFuncCalled bool + removeFunc func(catalogName string) error + getFuncCalled bool + getFunc func(catalogName, resolvedRef string) (fs.FS, error) +} + +func (m *mockCatalogCache) Remove(catalogName string) error { + m.removeFuncCalled = true + if m.removeFunc != nil { + return m.removeFunc(catalogName) + } + + return nil +} + +func (m *mockCatalogCache) Get(catalogName, resolvedRef string) (fs.FS, error) { + m.getFuncCalled = true + if m.getFunc != nil { + return m.getFunc(catalogName, resolvedRef) + } + + return nil, nil +} + +type mockCatalogCachePopulator struct { + populateCacheCalled bool + populateCacheFunc func(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) +} + +func (m *mockCatalogCachePopulator) PopulateCache(ctx context.Context, catalog *ocv1.ClusterCatalog) (fs.FS, error) { + m.populateCacheCalled = true + if m.populateCacheFunc != nil { + return m.populateCacheFunc(ctx, catalog) + } + + return nil, nil +} diff --git a/internal/controllers/clusterextension_admission_test.go b/internal/operator-controller/controllers/clusterextension_admission_test.go similarity index 71% rename from internal/controllers/clusterextension_admission_test.go rename to internal/operator-controller/controllers/clusterextension_admission_test.go index c7bc3ae1a2..38c6c60d41 100644 --- a/internal/controllers/clusterextension_admission_test.go +++ b/internal/operator-controller/controllers/clusterextension_admission_test.go @@ -6,13 +6,14 @@ import ( "testing" "github.com/stretchr/testify/require" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) func TestClusterExtensionSourceConfig(t *testing.T) { - sourceTypeEmptyError := "Invalid value: \"null\"" + sourceTypeEmptyError := "Invalid value: null" sourceTypeMismatchError := "spec.source.sourceType: Unsupported value" sourceConfigInvalidError := "spec.source: Invalid value" // unionField represents the required Catalog or (future) Bundle field required by SourceConfig @@ -36,31 +37,27 @@ func TestClusterExtensionSourceConfig(t *testing.T) { cl := newClient(t) var err error if tc.unionField == "Catalog" { - err = cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err = cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: tc.sourceType, - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: "test-package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) } if tc.unionField == "" { - err = cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err = cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: tc.sourceType, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) } @@ -76,8 +73,8 @@ func TestClusterExtensionSourceConfig(t *testing.T) { } func TestClusterExtensionAdmissionPackageName(t *testing.T) { - tooLongError := "spec.source.catalog.packageName: Too long: may not be longer than 253" - regexMismatchError := "spec.source.catalog.packageName in body should match" + tooLongError := "spec.source.catalog.packageName: Too long: may not be more than 253" + regexMismatchError := "packageName must be a valid DNS1123 subdomain" testCases := []struct { name string @@ -110,18 +107,16 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() cl := newClient(t) - err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: tc.pkgName, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -135,8 +130,8 @@ func TestClusterExtensionAdmissionPackageName(t *testing.T) { } func TestClusterExtensionAdmissionVersion(t *testing.T) { - tooLongError := "spec.source.catalog.version: Too long: may not be longer than 64" - regexMismatchError := "spec.source.catalog.version in body should match" + tooLongError := "spec.source.catalog.version: Too long: may not be more than 64" + regexMismatchError := "invalid version expression" testCases := []struct { name string @@ -209,19 +204,17 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() cl := newClient(t) - err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: "package", Version: tc.version, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -235,8 +228,8 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { } func TestClusterExtensionAdmissionChannel(t *testing.T) { - tooLongError := "spec.source.catalog.channels[0]: Too long: may not be longer than 253" - regexMismatchError := "spec.source.catalog.channels[0] in body should match" + tooLongError := "spec.source.catalog.channels[0]: Too long: may not be more than 253" + regexMismatchError := "channels entries must be valid DNS1123 subdomains" testCases := []struct { name string @@ -266,19 +259,17 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() cl := newClient(t) - err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: "package", Channels: tc.channels, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -292,8 +283,8 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { } func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { - tooLongError := "spec.install.namespace: Too long: may not be longer than 63" - regexMismatchError := "spec.install.namespace in body should match" + tooLongError := "spec.namespace: Too long: may not be more than 63" + regexMismatchError := "namespace must be a valid DNS1123 label" testCases := []struct { name string @@ -322,18 +313,16 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() cl := newClient(t) - err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: "package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: tc.namespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: "default", - }, + Namespace: tc.namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", }, })) if tc.errMsg == "" { @@ -347,8 +336,8 @@ func TestClusterExtensionAdmissionInstallNamespace(t *testing.T) { } func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { - tooLongError := "spec.install.serviceAccount.name: Too long: may not be longer than 253" - regexMismatchError := "spec.install.serviceAccount.name in body should match" + tooLongError := "spec.serviceAccount.name: Too long: may not be more than 253" + regexMismatchError := "name must be a valid DNS1123 subdomain" testCases := []struct { name string @@ -378,18 +367,16 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() cl := newClient(t) - err := cl.Create(context.Background(), buildClusterExtension(ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: "package", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: tc.serviceAccount, - }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: tc.serviceAccount, }, })) if tc.errMsg == "" { @@ -402,8 +389,116 @@ func TestClusterExtensionAdmissionServiceAccount(t *testing.T) { } } -func buildClusterExtension(spec ocv1alpha1.ClusterExtensionSpec) *ocv1alpha1.ClusterExtension { - return &ocv1alpha1.ClusterExtension{ +func TestClusterExtensionAdmissionInstall(t *testing.T) { + oneOfErrMsg := "at least one of [preflight] are required when install is specified" + + testCases := []struct { + name string + installConfig *ocv1.ClusterExtensionInstallConfig + errMsg string + }{ + { + name: "install specified, nothing configured", + installConfig: &ocv1.ClusterExtensionInstallConfig{}, + errMsg: oneOfErrMsg, + }, + { + name: "install specified, preflight configured", + installConfig: &ocv1.ClusterExtensionInstallConfig{ + Preflight: &ocv1.PreflightConfig{ + CRDUpgradeSafety: &ocv1.CRDUpgradeSafetyPreflightConfig{ + Enforcement: ocv1.CRDUpgradeSafetyEnforcementNone, + }, + }, + }, + errMsg: "", + }, + { + name: "install not specified", + installConfig: nil, + errMsg: "", + }, + } + + t.Parallel() + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + cl := newClient(t) + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "package", + }, + }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + Install: tc.installConfig, + })) + if tc.errMsg == "" { + require.NoError(t, err, "unexpected error for install configuration %v: %w", tc.installConfig, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMsg) + } + }) + } +} + +func Test_ClusterExtensionAdmissionInlineConfig(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + configBytes []byte + errMsg string + }{ + { + name: "rejects valid json that is not of an object type", + configBytes: []byte(`true`), + errMsg: "spec.config.inline in body must be of type object", + }, + { + name: "accepts valid json object", + configBytes: []byte(`{"key": "value"}`), + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + cl := newClient(t) + err := cl.Create(context.Background(), buildClusterExtension(ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "package", + }, + }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + Config: &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: tc.configBytes, + }, + }, + })) + if tc.errMsg == "" { + require.NoError(t, err, "unexpected error for inline bundle configuration %q: %w", string(tc.configBytes), err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMsg) + } + }) + } +} + +func buildClusterExtension(spec ocv1.ClusterExtensionSpec) *ocv1.ClusterExtension { + return &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-extension-", }, diff --git a/internal/operator-controller/controllers/clusterextension_controller.go b/internal/operator-controller/controllers/clusterextension_controller.go new file mode 100644 index 0000000000..7bcedde656 --- /dev/null +++ b/internal/operator-controller/controllers/clusterextension_controller.go @@ -0,0 +1,572 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "cmp" + "context" + "errors" + "fmt" + "io/fs" + "slices" + "strings" + + "github.com/go-logr/logr" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + "k8s.io/apimachinery/pkg/api/equality" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + crhandler "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/conditionsets" + "github.com/operator-framework/operator-controller/internal/operator-controller/labels" + "github.com/operator-framework/operator-controller/internal/operator-controller/resolve" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" +) + +const ( + ClusterExtensionCleanupUnpackCacheFinalizer = "olm.operatorframework.io/cleanup-unpack-cache" + ClusterExtensionCleanupContentManagerCacheFinalizer = "olm.operatorframework.io/cleanup-contentmanager-cache" +) + +// ClusterExtensionReconciler reconciles a ClusterExtension object +type ClusterExtensionReconciler struct { + client.Client + Resolver resolve.Resolver + + ImageCache imageutil.Cache + ImagePuller imageutil.Puller + + StorageMigrator StorageMigrator + Applier Applier + RevisionStatesGetter RevisionStatesGetter + Finalizers crfinalizer.Finalizers +} + +type StorageMigrator interface { + Migrate(context.Context, *ocv1.ClusterExtension, map[string]string) error +} + +type Applier interface { + // Apply applies the content in the provided fs.FS using the configuration of the provided ClusterExtension. + // It also takes in a map[string]string to be applied to all applied resources as labels and another + // map[string]string used to create a unique identifier for a stored reference to the resources created. + Apply(context.Context, fs.FS, *ocv1.ClusterExtension, map[string]string, map[string]string) (bool, string, error) +} + +type RevisionStatesGetter interface { + GetRevisionStates(ctx context.Context, ext *ocv1.ClusterExtension) (*RevisionStates, error) +} + +// The operator controller needs to watch all the bundle objects and reconcile accordingly. Though not ideal, but these permissions are required. +// This has been taken from rukpak, and an issue was created before to discuss it: https://github.com/operator-framework/rukpak/issues/800. +func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx).WithName("cluster-extension") + ctx = log.IntoContext(ctx, l) + + existingExt := &ocv1.ClusterExtension{} + if err := r.Get(ctx, req.NamespacedName, existingExt); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + l.Info("reconcile starting") + defer l.Info("reconcile ending") + + reconciledExt := existingExt.DeepCopy() + res, reconcileErr := r.reconcile(ctx, reconciledExt) + + // Do checks before any Update()s, as Update() may modify the resource structure! + updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) + updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers) + + // If any unexpected fields have changed, panic before updating the resource + unexpectedFieldsChanged := checkForUnexpectedClusterExtensionFieldChange(*existingExt, *reconciledExt) + if unexpectedFieldsChanged { + panic("spec or metadata changed by reconciler") + } + + // Save the finalizers off to the side. If we update the status, the reconciledExt will be updated + // to contain the new state of the ClusterExtension, which contains the status update, but (critically) + // does not contain the finalizers. After the status update, we need to re-add the finalizers to the + // reconciledExt before updating the object. + finalizers := reconciledExt.Finalizers + if updateStatus { + if err := r.Client.Status().Update(ctx, reconciledExt); err != nil { + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err)) + } + } + reconciledExt.Finalizers = finalizers + + if updateFinalizers { + if err := r.Update(ctx, reconciledExt); err != nil { + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err)) + } + } + + return res, reconcileErr +} + +// ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension, +// and assigns a specified reason and custom message to any missing condition. +func ensureAllConditionsWithReason(ext *ocv1.ClusterExtension, reason v1alpha1.ConditionReason, message string) { + for _, condType := range conditionsets.ConditionTypes { + cond := apimeta.FindStatusCondition(ext.Status.Conditions, condType) + if cond == nil { + // Create a new condition with a valid reason and add it + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: condType, + Status: metav1.ConditionFalse, + Reason: string(reason), + Message: message, + ObservedGeneration: ext.GetGeneration(), + }) + } + } +} + +// Compare resources - ignoring status & metadata.finalizers +func checkForUnexpectedClusterExtensionFieldChange(a, b ocv1.ClusterExtension) bool { + a.Status, b.Status = ocv1.ClusterExtensionStatus{}, ocv1.ClusterExtensionStatus{} + a.Finalizers, b.Finalizers = []string{}, []string{} + return !equality.Semantic.DeepEqual(a, b) +} + +// Helper function to do the actual reconcile +// +// Today we always return ctrl.Result{} and an error. +// But in the future we might update this function +// to return different results (e.g. requeue). +// +/* The reconcile functions performs the following major tasks: +1. Resolution: Run the resolution to find the bundle from the catalog which needs to be installed. +2. Validate: Ensure that the bundle returned from the resolution for install meets our requirements. +3. Unpack: Unpack the contents from the bundle and store in a localdir in the pod. +4. Install: The process of installing involves: +4.1 Converting the CSV in the bundle into a set of plain k8s objects. +4.2 Generating a chart from k8s objects. +4.3 Store the release on cluster. +*/ +//nolint:unparam +func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.ClusterExtension) (ctrl.Result, error) { + l := log.FromContext(ctx) + + l.Info("handling finalizers") + finalizeResult, err := r.Finalizers.Finalize(ctx, ext) + if err != nil { + setStatusProgressing(ext, err) + return ctrl.Result{}, err + } + if finalizeResult.Updated || finalizeResult.StatusUpdated { + // On create: make sure the finalizer is applied before we do anything + // On delete: make sure we do nothing after the finalizer is removed + return ctrl.Result{}, nil + } + + if ext.GetDeletionTimestamp() != nil { + // If we've gotten here, that means the cluster extension is being deleted, we've handled all of + // _our_ finalizers (above), but the cluster extension is still present in the cluster, likely + // because there are _other_ finalizers that other controllers need to handle, (e.g. the orphan + // deletion finalizer). + return ctrl.Result{}, nil + } + + objLbls := map[string]string{ + labels.OwnerKindKey: ocv1.ClusterExtensionKind, + labels.OwnerNameKey: ext.GetName(), + } + + if r.StorageMigrator != nil { + if err := r.StorageMigrator.Migrate(ctx, ext, objLbls); err != nil { + return ctrl.Result{}, fmt.Errorf("migrating storage: %w", err) + } + } + + l.Info("getting installed bundle") + revisionStates, err := r.RevisionStatesGetter.GetRevisionStates(ctx, ext) + if err != nil { + setInstallStatus(ext, nil) + var saerr *authentication.ServiceAccountNotFoundError + if errors.As(err, &saerr) { + setInstalledStatusConditionUnknown(ext, saerr.Error()) + setStatusProgressing(ext, errors.New("installation cannot proceed due to missing ServiceAccount")) + return ctrl.Result{}, err + } + setInstalledStatusConditionUnknown(ext, err.Error()) + setStatusProgressing(ext, errors.New("retrying to get installed bundle")) + return ctrl.Result{}, err + } + + var resolvedRevisionMetadata *RevisionMetadata + if len(revisionStates.RollingOut) == 0 { + l.Info("resolving bundle") + var bm *ocv1.BundleMetadata + if revisionStates.Installed != nil { + bm = &revisionStates.Installed.BundleMetadata + } + resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.Resolver.Resolve(ctx, ext, bm) + if err != nil { + // Note: We don't distinguish between resolution-specific errors and generic errors + setStatusProgressing(ext, err) + setInstalledStatusFromRevisionStates(ext, revisionStates) + ensureAllConditionsWithReason(ext, ocv1.ReasonFailed, err.Error()) + return ctrl.Result{}, err + } + + // set deprecation status after _successful_ resolution + // TODO: + // 1. It seems like deprecation status should reflect the currently installed bundle, not the resolved + // bundle. So perhaps we should set package and channel deprecations directly after resolution, but + // defer setting the bundle deprecation until we successfully install the bundle. + // 2. If resolution fails because it can't find a bundle, that doesn't mean we wouldn't be able to find + // a deprecation for the ClusterExtension's spec.packageName. Perhaps we should check for a non-nil + // resolvedDeprecation even if resolution returns an error. If present, we can still update some of + // our deprecation status. + // - Open question though: what if different catalogs have different opinions of what's deprecated. + // If we can't resolve a bundle, how do we know which catalog to trust for deprecation information? + // Perhaps if the package shows up in multiple catalogs and deprecations don't match, we can set + // the deprecation status to unknown? Or perhaps we somehow combine the deprecation information from + // all catalogs? + SetDeprecationStatus(ext, resolvedBundle.Name, resolvedDeprecation) + resolvedRevisionMetadata = &RevisionMetadata{ + Package: resolvedBundle.Package, + Image: resolvedBundle.Image, + BundleMetadata: bundleutil.MetadataFor(resolvedBundle.Name, *resolvedBundleVersion), + } + } else { + resolvedRevisionMetadata = revisionStates.RollingOut[0] + } + + l.Info("unpacking resolved bundle") + imageFS, _, _, err := r.ImagePuller.Pull(ctx, ext.GetName(), resolvedRevisionMetadata.Image, r.ImageCache) + if err != nil { + // Wrap the error passed to this with the resolution information until we have successfully + // installed since we intend for the progressing condition to replace the resolved condition + // and will be removing the .status.resolution field from the ClusterExtension status API + setStatusProgressing(ext, wrapErrorWithResolutionInfo(resolvedRevisionMetadata.BundleMetadata, err)) + setInstalledStatusFromRevisionStates(ext, revisionStates) + return ctrl.Result{}, err + } + + storeLbls := map[string]string{ + labels.BundleNameKey: resolvedRevisionMetadata.Name, + labels.PackageNameKey: resolvedRevisionMetadata.Package, + labels.BundleVersionKey: resolvedRevisionMetadata.Version, + labels.BundleReferenceKey: resolvedRevisionMetadata.Image, + } + + l.Info("applying bundle contents") + // NOTE: We need to be cautious of eating errors here. + // We should always return any error that occurs during an + // attempt to apply content to the cluster. Only when there is + // a verifiable reason to eat the error (i.e it is recoverable) + // should an exception be made. + // The following kinds of errors should be returned up the stack + // to ensure exponential backoff can occur: + // - Permission errors (it is not possible to watch changes to permissions. + // The only way to eventually recover from permission errors is to keep retrying). + rolloutSucceeded, rolloutStatus, err := r.Applier.Apply(ctx, imageFS, ext, objLbls, storeLbls) + + // Set installed status + if rolloutSucceeded { + revisionStates = &RevisionStates{Installed: resolvedRevisionMetadata} + } else if err == nil && revisionStates.Installed == nil && len(revisionStates.RollingOut) == 0 { + revisionStates = &RevisionStates{RollingOut: []*RevisionMetadata{resolvedRevisionMetadata}} + } + setInstalledStatusFromRevisionStates(ext, revisionStates) + + // If there was an error applying the resolved bundle, + // report the error via the Progressing condition. + if err != nil { + setStatusProgressing(ext, wrapErrorWithResolutionInfo(resolvedRevisionMetadata.BundleMetadata, err)) + return ctrl.Result{}, err + } else if !rolloutSucceeded { + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRolloutInProgress, + Message: rolloutStatus, + ObservedGeneration: ext.GetGeneration(), + }) + } else { + setStatusProgressing(ext, nil) + } + return ctrl.Result{}, nil +} + +// SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension +// based on the provided bundle +func SetDeprecationStatus(ext *ocv1.ClusterExtension, bundleName string, deprecation *declcfg.Deprecation) { + deprecations := map[string][]declcfg.DeprecationEntry{} + channelSet := sets.New[string]() + if ext.Spec.Source.Catalog != nil { + for _, channel := range ext.Spec.Source.Catalog.Channels { + channelSet.Insert(channel) + } + } + if deprecation != nil { + for _, entry := range deprecation.Entries { + switch entry.Reference.Schema { + case declcfg.SchemaPackage: + deprecations[ocv1.TypePackageDeprecated] = []declcfg.DeprecationEntry{entry} + case declcfg.SchemaChannel: + if channelSet.Has(entry.Reference.Name) { + deprecations[ocv1.TypeChannelDeprecated] = append(deprecations[ocv1.TypeChannelDeprecated], entry) + } + case declcfg.SchemaBundle: + if bundleName != entry.Reference.Name { + continue + } + deprecations[ocv1.TypeBundleDeprecated] = []declcfg.DeprecationEntry{entry} + } + } + } + + // first get ordered deprecation messages that we'll join in the Deprecated condition message + var deprecationMessages []string + for _, conditionType := range []string{ + ocv1.TypePackageDeprecated, + ocv1.TypeChannelDeprecated, + ocv1.TypeBundleDeprecated, + } { + if entries, ok := deprecations[conditionType]; ok { + for _, entry := range entries { + deprecationMessages = append(deprecationMessages, entry.Message) + } + } + } + + // next, set the Deprecated condition + status, reason, message := metav1.ConditionFalse, ocv1.ReasonDeprecated, "" + if len(deprecationMessages) > 0 { + status, reason, message = metav1.ConditionTrue, ocv1.ReasonDeprecated, strings.Join(deprecationMessages, ";") + } + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeDeprecated, + Reason: reason, + Status: status, + Message: message, + ObservedGeneration: ext.Generation, + }) + + // finally, set the individual deprecation conditions for package, channel, and bundle + for _, conditionType := range []string{ + ocv1.TypePackageDeprecated, + ocv1.TypeChannelDeprecated, + ocv1.TypeBundleDeprecated, + } { + entries, ok := deprecations[conditionType] + status, reason, message := metav1.ConditionFalse, ocv1.ReasonDeprecated, "" + if ok { + status, reason = metav1.ConditionTrue, ocv1.ReasonDeprecated + for _, entry := range entries { + message = fmt.Sprintf("%s\n%s", message, entry.Message) + } + } + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: conditionType, + Reason: reason, + Status: status, + Message: message, + ObservedGeneration: ext.Generation, + }) + } +} + +type ControllerBuilderOption func(builder *ctrl.Builder) + +func WithOwns(obj client.Object) ControllerBuilderOption { + return func(builder *ctrl.Builder) { + builder.Owns(obj) + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager, opts ...ControllerBuilderOption) (crcontroller.Controller, error) { + ctrlBuilder := ctrl.NewControllerManagedBy(mgr). + For(&ocv1.ClusterExtension{}). + Named("controller-operator-cluster-extension-controller"). + Watches(&ocv1.ClusterCatalog{}, + crhandler.EnqueueRequestsFromMapFunc(clusterExtensionRequestsForCatalog(mgr.GetClient(), mgr.GetLogger())), + builder.WithPredicates(predicate.Funcs{ + UpdateFunc: func(ue event.UpdateEvent) bool { + oldObject, isOldCatalog := ue.ObjectOld.(*ocv1.ClusterCatalog) + newObject, isNewCatalog := ue.ObjectNew.(*ocv1.ClusterCatalog) + + if !isOldCatalog || !isNewCatalog { + return true + } + + if oldObject.Status.ResolvedSource != nil && newObject.Status.ResolvedSource != nil { + if oldObject.Status.ResolvedSource.Image != nil && newObject.Status.ResolvedSource.Image != nil { + return oldObject.Status.ResolvedSource.Image.Ref != newObject.Status.ResolvedSource.Image.Ref + } + } + return true + }, + })) + + for _, applyOpt := range opts { + applyOpt(ctrlBuilder) + } + + return ctrlBuilder.Build(r) +} + +func wrapErrorWithResolutionInfo(resolved ocv1.BundleMetadata, err error) error { + return fmt.Errorf("error for resolved bundle %q with version %q: %w", resolved.Name, resolved.Version, err) +} + +// Generate reconcile requests for all cluster extensions affected by a catalog change +func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) crhandler.MapFunc { + return func(ctx context.Context, _ client.Object) []reconcile.Request { + // no way of associating an extension to a catalog so create reconcile requests for everything + clusterExtensions := metav1.PartialObjectMetadataList{} + clusterExtensions.SetGroupVersionKind(ocv1.GroupVersion.WithKind("ClusterExtensionList")) + err := c.List(ctx, &clusterExtensions) + if err != nil { + logger.Error(err, "unable to enqueue cluster extensions for catalog reconcile") + return nil + } + var requests []reconcile.Request + for _, ext := range clusterExtensions.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: ext.GetNamespace(), + Name: ext.GetName(), + }, + }) + } + return requests + } +} + +type RevisionMetadata struct { + Package string + Image string + ocv1.BundleMetadata +} + +type RevisionStates struct { + Installed *RevisionMetadata + RollingOut []*RevisionMetadata +} + +type HelmRevisionStatesGetter struct { + helmclient.ActionClientGetter +} + +func (d *HelmRevisionStatesGetter) GetRevisionStates(ctx context.Context, ext *ocv1.ClusterExtension) (*RevisionStates, error) { + cl, err := d.ActionClientFor(ctx, ext) + if err != nil { + return nil, err + } + + relhis, err := cl.History(ext.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, err + } + rs := &RevisionStates{} + if len(relhis) == 0 { + return rs, nil + } + + // relhis[0].Info.Status is the status of the most recent install attempt. + // But we need to look for the most-recent _Deployed_ release + for _, rel := range relhis { + if rel.Info != nil && rel.Info.Status == release.StatusDeployed { + rs.Installed = &RevisionMetadata{ + Package: rel.Labels[labels.PackageNameKey], + Image: rel.Labels[labels.BundleReferenceKey], + BundleMetadata: ocv1.BundleMetadata{ + Name: rel.Labels[labels.BundleNameKey], + Version: rel.Labels[labels.BundleVersionKey], + }, + } + break + } + } + return rs, nil +} + +type BoxcutterRevisionStatesGetter struct { + Reader client.Reader +} + +func (d *BoxcutterRevisionStatesGetter) GetRevisionStates(ctx context.Context, ext *ocv1.ClusterExtension) (*RevisionStates, error) { + // TODO: boxcutter applier has a nearly identical bit of code for listing and sorting revisions + // only difference here is that it sorts in reverse order to start iterating with the most + // recent revisions. We should consolidate to avoid code duplication. + existingRevisionList := &ocv1.ClusterExtensionRevisionList{} + if err := d.Reader.List(ctx, existingRevisionList, client.MatchingLabels{ + ClusterExtensionRevisionOwnerLabel: ext.Name, + }); err != nil { + return nil, fmt.Errorf("listing revisions: %w", err) + } + slices.SortFunc(existingRevisionList.Items, func(a, b ocv1.ClusterExtensionRevision) int { + return cmp.Compare(a.Spec.Revision, b.Spec.Revision) + }) + + rs := &RevisionStates{} + for _, rev := range existingRevisionList.Items { + switch rev.Spec.LifecycleState { + case ocv1.ClusterExtensionRevisionLifecycleStateActive, + ocv1.ClusterExtensionRevisionLifecycleStatePaused: + default: + // Skip anything not active or paused, which should only be "Archived". + continue + } + + // TODO: the setting of these annotations (happens in boxcutter applier when we pass in "storageLabels") + // is fairly decoupled from this code where we get the annotations back out. We may want to co-locate + // the set/get logic a bit better to make it more maintainable and less likely to get out of sync. + rm := &RevisionMetadata{ + Package: rev.Labels[labels.PackageNameKey], + Image: rev.Annotations[labels.BundleReferenceKey], + BundleMetadata: ocv1.BundleMetadata{ + Name: rev.Annotations[labels.BundleNameKey], + Version: rev.Annotations[labels.BundleVersionKey], + }, + } + + if apimeta.IsStatusConditionTrue(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) { + rs.Installed = rm + } else { + rs.RollingOut = append(rs.RollingOut, rm) + } + } + + return rs, nil +} diff --git a/internal/operator-controller/controllers/clusterextension_controller_test.go b/internal/operator-controller/controllers/clusterextension_controller_test.go new file mode 100644 index 0000000000..437f62dcec --- /dev/null +++ b/internal/operator-controller/controllers/clusterextension_controller_test.go @@ -0,0 +1,1575 @@ +package controllers_test + +import ( + "context" + "errors" + "fmt" + "testing" + "testing/fstest" + + bsemver "github.com/blang/semver/v4" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage/driver" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/authentication" + "github.com/operator-framework/operator-controller/internal/operator-controller/conditionsets" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/internal/operator-controller/finalizers" + "github.com/operator-framework/operator-controller/internal/operator-controller/labels" + "github.com/operator-framework/operator-controller/internal/operator-controller/resolve" + imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" +) + +// Describe: ClusterExtension Controller Test +func TestClusterExtensionDoesNotExist(t *testing.T) { + _, reconciler := newClientAndReconciler(t) + + t.Log("When the cluster extension does not exist") + t.Log("It returns no error") + res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) +} + +func TestClusterExtensionShortCircuitsReconcileDuringDeletion(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + + installedBundleGetterCalledErr := errors.New("revision states getter called") + checkInstalledBundleGetterCalled := func(t require.TestingT, err error, args ...interface{}) { + require.Equal(t, installedBundleGetterCalledErr, err) + } + reconciler.RevisionStatesGetter = &MockRevisionStatesGetter{ + Err: installedBundleGetterCalledErr, + } + + type testCase struct { + name string + finalizers []string + shouldDelete bool + expectErr require.ErrorAssertionFunc + } + for _, tc := range []testCase{ + { + name: "no finalizers, not deleted", + expectErr: checkInstalledBundleGetterCalled, + }, + { + name: "has finalizers, not deleted", + finalizers: []string{"finalizer"}, + expectErr: checkInstalledBundleGetterCalled, + }, + { + name: "has finalizers, deleted", + finalizers: []string{"finalizer"}, + shouldDelete: true, + expectErr: require.NoError, + }, + } { + t.Run(tc.name, func(t *testing.T) { + pkgName := fmt.Sprintf("test-pkg-%s", rand.String(6)) + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a non-existent package") + t.Log("By initializing cluster state") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: extKey.Name, + Finalizers: tc.finalizers, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + }, + }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + require.NoError(t, cl.Create(ctx, clusterExtension)) + if tc.shouldDelete { + require.NoError(t, cl.Delete(ctx, clusterExtension)) + } + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + tc.expectErr(t, err) + }) + } +} + +func TestClusterExtensionResolutionFails(t *testing.T) { + pkgName := fmt.Sprintf("non-existent-%s", rand.String(6)) + cl, reconciler := newClientAndReconciler(t) + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + return nil, nil, nil, fmt.Errorf("no package %q found", pkgName) + }) + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a non-existent package") + t.Log("By initializing cluster state") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + }, + }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "default", + }, + }, + } + require.NoError(t, cl.Create(ctx, clusterExtension)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q found", pkgName)) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + require.Empty(t, clusterExtension.Status.Install) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, ocv1.ReasonRetrying, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q found", pkgName), cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, clusterExtension) + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionResolutionSuccessfulUnpackFails(t *testing.T) { + type testCase struct { + name string + pullErr error + expectTerminal bool + } + for _, tc := range []testCase{ + { + name: "non-terminal pull failure", + pullErr: errors.New("pull failure"), + }, + { + name: "terminal pull failure", + pullErr: reconcile.TerminalError(errors.New("terminal pull failure")), + expectTerminal: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + Error: tc.pullErr, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + isTerminal := errors.Is(err, reconcile.TerminalError(nil)) + assert.Equal(t, tc.expectTerminal, isTerminal, "expected terminal error: %v, got: %v", tc.expectTerminal, isTerminal) + require.ErrorContains(t, err, tc.pullErr.Error()) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + expectedBundleMetadata := ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} + require.Empty(t, clusterExtension.Status.Install) + + t.Log("By checking the expected conditions") + expectStatus := metav1.ConditionTrue + expectReason := ocv1.ReasonRetrying + if tc.expectTerminal { + expectStatus = metav1.ConditionFalse + expectReason = ocv1.ReasonBlocked + } + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, expectStatus, progressingCond.Status) + require.Equal(t, expectReason, progressingCond.Reason) + require.Contains(t, progressingCond.Message, fmt.Sprintf("for resolved bundle %q with version %q", expectedBundleMetadata.Name, expectedBundleMetadata.Version)) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) + }) + } +} + +func TestClusterExtensionResolutionAndUnpackSuccessfulApplierFails(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + reconciler.Applier = &MockApplier{ + err: errors.New("apply failure"), + } + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + expectedBundleMetadata := ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} + require.Empty(t, clusterExtension.Status.Install) + + t.Log("By checking the expected installed conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionFalse, installedCond.Status) + require.Equal(t, ocv1.ReasonFailed, installedCond.Reason) + + t.Log("By checking the expected progressing conditions") + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonRetrying, progressingCond.Reason) + require.Contains(t, progressingCond.Message, fmt.Sprintf("for resolved bundle %q with version %q", expectedBundleMetadata.Name, expectedBundleMetadata.Version)) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionServiceAccountNotFound(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.RevisionStatesGetter = &MockRevisionStatesGetter{ + Err: &authentication.ServiceAccountNotFoundError{ + ServiceAccountName: "missing-sa", + ServiceAccountNamespace: "default", + }} + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("Given a cluster extension with a missing service account") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test-package", + }, + }, + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "missing-sa", + }, + }, + } + + require.NoError(t, cl.Create(ctx, clusterExtension)) + + t.Log("When reconciling the cluster extension") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + require.IsType(t, &authentication.ServiceAccountNotFoundError{}, err) + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionUnknown, installedCond.Status) + require.Contains(t, installedCond.Message, fmt.Sprintf("service account %q not found in namespace %q: unable to authenticate with the Kubernetes cluster.", + "missing-sa", "default")) + + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonRetrying, progressingCond.Reason) + require.Contains(t, progressingCond.Message, "installation cannot proceed due to missing ServiceAccount") + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionApplierFailsWithBundleInstalled(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + + reconciler.RevisionStatesGetter = &MockRevisionStatesGetter{ + RevisionStates: &controllers.RevisionStates{ + Installed: &controllers.RevisionMetadata{ + BundleMetadata: ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, + }, + } + reconciler.Applier = &MockApplier{ + installCompleted: true, + } + + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + reconciler.Applier = &MockApplier{ + err: errors.New("apply failure"), + } + + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + expectedBundleMetadata := ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} + require.Equal(t, expectedBundleMetadata, clusterExtension.Status.Install.Bundle) + + t.Log("By checking the expected installed conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionTrue, installedCond.Status) + require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason) + + t.Log("By checking the expected progressing conditions") + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonRetrying, progressingCond.Reason) + require.Contains(t, progressingCond.Message, fmt.Sprintf("for resolved bundle %q with version %q", expectedBundleMetadata.Name, expectedBundleMetadata.Version)) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionManagerFailed(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + reconciler.Applier = &MockApplier{ + installCompleted: true, + err: errors.New("manager fail"), + } + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + require.Equal(t, ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) + + t.Log("By checking the expected installed conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionTrue, installedCond.Status) + require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason) + + t.Log("By checking the expected progressing conditions") + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonRetrying, progressingCond.Reason) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionManagedContentCacheWatchFail(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: ocv1.SourceTypeCatalog, + + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: installNamespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + reconciler.Applier = &MockApplier{ + installCompleted: true, + err: errors.New("watch error"), + } + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + require.Equal(t, ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) + + t.Log("By checking the expected installed conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionTrue, installedCond.Status) + require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason) + + t.Log("By checking the expected progressing conditions") + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonRetrying, progressingCond.Reason) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionInstallationSucceeds(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + reconciler.Applier = &MockApplier{ + installCompleted: true, + } + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated cluster extension after reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + + t.Log("By checking the status fields") + require.Equal(t, ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle) + + t.Log("By checking the expected installed conditions") + installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, installedCond) + require.Equal(t, metav1.ConditionTrue, installedCond.Status) + require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason) + + t.Log("By checking the expected progressing conditions") + progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond) + require.Equal(t, metav1.ConditionTrue, progressingCond.Status) + require.Equal(t, ocv1.ReasonSucceeded, progressingCond.Reason) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) +} + +func TestClusterExtensionDeleteFinalizerFails(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + reconciler.ImagePuller = &imageutil.MockPuller{ + ImageFS: fstest.MapFS{}, + } + + ctx := context.Background() + extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} + + t.Log("When the cluster extension specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + namespace := fmt.Sprintf("test-ns-%s", rand.String(8)) + serviceAccount := fmt.Sprintf("test-sa-%s", rand.String(8)) + + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: pkgName, + Version: pkgVer, + Channels: []string{pkgChan}, + }, + }, + Namespace: namespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount, + }, + }, + } + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) + t.Log("It sets resolution success status") + t.Log("By running reconcile") + reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + v := bsemver.MustParse("1.0.0") + return &declcfg.Bundle{ + Name: "prometheus.v1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, &v, nil, nil + }) + fakeFinalizer := "fake.testfinalizer.io" + finalizersMessage := "still have finalizers" + reconciler.Applier = &MockApplier{ + installCompleted: true, + } + reconciler.RevisionStatesGetter = &MockRevisionStatesGetter{ + RevisionStates: &controllers.RevisionStates{ + Installed: &controllers.RevisionMetadata{ + BundleMetadata: ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + }, + }, + } + err = reconciler.Finalizers.Register(fakeFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + return crfinalizer.Result{}, errors.New(finalizersMessage) + })) + + require.NoError(t, err) + + // Reconcile twice to simulate installing the ClusterExtension and loading in the finalizers + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated cluster extension after first reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + expectedBundleMetadata := ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"} + require.Equal(t, expectedBundleMetadata, clusterExtension.Status.Install.Bundle) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + + require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})) + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) + require.Error(t, err, res) + + t.Log("By fetching updated cluster extension after second reconcile") + require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) + cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.Equal(t, expectedBundleMetadata, clusterExtension.Status.Install.Bundle) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, fakeFinalizer, clusterExtension.Finalizers[0]) + cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Contains(t, cond.Message, finalizersMessage) +} + +func verifyInvariants(ctx context.Context, t *testing.T, c client.Client, ext *ocv1.ClusterExtension) { + key := client.ObjectKeyFromObject(ext) + require.NoError(t, c.Get(ctx, key, ext)) + + verifyConditionsInvariants(t, ext) +} + +func verifyConditionsInvariants(t *testing.T, ext *ocv1.ClusterExtension) { + // Expect that the cluster extension's set of conditions contains all defined + // condition types for the ClusterExtension API. Every reconcile should always + // ensure every condition type's status/reason/message reflects the state + // read during _this_ reconcile call. + require.Len(t, ext.Status.Conditions, len(conditionsets.ConditionTypes)) + for _, tt := range conditionsets.ConditionTypes { + cond := apimeta.FindStatusCondition(ext.Status.Conditions, tt) + require.NotNil(t, cond) + require.NotEmpty(t, cond.Status) + require.Contains(t, conditionsets.ConditionReasons, cond.Reason) + require.Equal(t, ext.GetGeneration(), cond.ObservedGeneration) + } +} + +func TestSetDeprecationStatus(t *testing.T) { + for _, tc := range []struct { + name string + clusterExtension *ocv1.ClusterExtension + expectedClusterExtension *ocv1.ClusterExtension + bundle *declcfg.Bundle + deprecation *declcfg.Deprecation + }{ + { + name: "no deprecations, all deprecation statuses set to False", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: nil, + }, + { + name: "deprecated channel, but no channel specified, all deprecation statuses set to False", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{}, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{}, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{{ + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + }}, + }, + }, + { + name: "deprecated channel, but a non-deprecated channel specified, all deprecation statuses set to False", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"nondeprecated"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"nondeprecated"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + }, + }, + }, + }, + { + name: "deprecated channel specified, ChannelDeprecated and Deprecated status set to true, others set to false", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + }, + }, + }, + { + name: "deprecated package and channel specified, deprecated bundle, all deprecation statuses set to true", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{Name: "badbundle"}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaPackage, + }, + Message: "bad package!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaBundle, + Name: "badbundle", + }, + Message: "bad bundle!", + }, + }, + }, + }, + { + name: "deprecated channel specified, deprecated bundle, all deprecation statuses set to true, all deprecation statuses set to true except PackageDeprecated", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{Name: "badbundle"}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaBundle, + Name: "badbundle", + }, + Message: "bad bundle!", + }, + }, + }, + }, + { + name: "deprecated package and channel specified, all deprecation statuses set to true except BundleDeprecated", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaPackage, + }, + Message: "bad package!", + }, + }, + }, + }, + { + name: "deprecated channels specified, ChannelDeprecated and Deprecated status set to true, others set to false", + clusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel", "anotherbadchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + Channels: []string{"badchannel", "anotherbadchannel"}, + }, + }, + }, + Status: ocv1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1.TypeDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypePackageDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeChannelDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1.TypeBundleDeprecated, + Reason: ocv1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "anotherbadchannel", + }, + Message: "another bad channedl!", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + controllers.SetDeprecationStatus(tc.clusterExtension, tc.bundle.Name, tc.deprecation) + // TODO: we should test for unexpected changes to lastTransitionTime. We only expect + // lastTransitionTime to change when the status of the condition changes. + assert.Empty(t, cmp.Diff(tc.expectedClusterExtension, tc.clusterExtension, cmpopts.IgnoreFields(metav1.Condition{}, "Message", "LastTransitionTime"))) + }) + } +} + +type MockActionGetter struct { + description string + rels []*release.Release + err error + expectedInstalled *controllers.RevisionMetadata + expectedError error +} + +func (mag *MockActionGetter) ActionClientFor(ctx context.Context, obj client.Object) (helmclient.ActionInterface, error) { + return mag, nil +} + +func (mag *MockActionGetter) Get(name string, opts ...helmclient.GetOption) (*release.Release, error) { + return nil, nil +} + +// This is the function we are really testing +func (mag *MockActionGetter) History(name string, opts ...helmclient.HistoryOption) ([]*release.Release, error) { + return mag.rels, mag.err +} + +func (mag *MockActionGetter) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.InstallOption) (*release.Release, error) { + return nil, nil +} + +func (mag *MockActionGetter) Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.UpgradeOption) (*release.Release, error) { + return nil, nil +} + +func (mag *MockActionGetter) Uninstall(name string, opts ...helmclient.UninstallOption) (*release.UninstallReleaseResponse, error) { + return nil, nil +} + +func (mag *MockActionGetter) Reconcile(rel *release.Release) error { + return nil +} + +func TestGetInstalledBundleHistory(t *testing.T) { + getter := controllers.HelmRevisionStatesGetter{} + + ext := ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ext", + }, + } + + mag := []MockActionGetter{ + { + "No return", + nil, nil, + nil, nil, + }, + { + "ErrReleaseNotFound (special case)", + nil, driver.ErrReleaseNotFound, + nil, nil, + }, + { + "Error from History", + nil, fmt.Errorf("generic error"), + nil, fmt.Errorf("generic error"), + }, + { + "One item in history", + []*release.Release{ + { + Name: "test-ext", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + Labels: map[string]string{ + labels.BundleNameKey: "test-ext", + labels.BundleVersionKey: "1.0", + labels.BundleReferenceKey: "bundle-ref", + }, + }, + }, + nil, + &controllers.RevisionMetadata{ + BundleMetadata: ocv1.BundleMetadata{ + Name: "test-ext", + Version: "1.0", + }, + Image: "bundle-ref", + }, nil, + }, + { + "Two items in history", + []*release.Release{ + { + Name: "test-ext", + Info: &release.Info{ + Status: release.StatusFailed, + }, + Labels: map[string]string{ + labels.BundleNameKey: "test-ext", + labels.BundleVersionKey: "2.0", + labels.BundleReferenceKey: "bundle-ref-2", + }, + }, + { + Name: "test-ext", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + Labels: map[string]string{ + labels.BundleNameKey: "test-ext", + labels.BundleVersionKey: "1.0", + labels.BundleReferenceKey: "bundle-ref-1", + }, + }, + }, + nil, + &controllers.RevisionMetadata{ + BundleMetadata: ocv1.BundleMetadata{ + Name: "test-ext", + Version: "1.0", + }, + Image: "bundle-ref-1", + }, nil, + }, + } + + for _, tst := range mag { + t.Log(tst.description) + getter.ActionClientGetter = &tst + md, err := getter.GetRevisionStates(context.Background(), &ext) + if tst.expectedError != nil { + require.Equal(t, tst.expectedError, err) + require.Nil(t, md) + } else { + require.NoError(t, err) + require.Equal(t, tst.expectedInstalled, md.Installed) + require.Nil(t, md.RollingOut) + } + } +} diff --git a/internal/operator-controller/controllers/clusterextensionrevision_controller.go b/internal/operator-controller/controllers/clusterextensionrevision_controller.go new file mode 100644 index 0000000000..8882491615 --- /dev/null +++ b/internal/operator-controller/controllers/clusterextensionrevision_controller.go @@ -0,0 +1,541 @@ +//go:build !standard + +package controllers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "pkg.package-operator.run/boxcutter" + "pkg.package-operator.run/boxcutter/machinery" + machinerytypes "pkg.package-operator.run/boxcutter/machinery/types" + "pkg.package-operator.run/boxcutter/probing" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +const ( + ClusterExtensionRevisionOwnerLabel = "olm.operatorframework.io/owner" + clusterExtensionRevisionTeardownFinalizer = "olm.operatorframework.io/teardown" +) + +// ClusterExtensionRevisionReconciler actions individual snapshots of ClusterExtensions, +// as part of the boxcutter integration. +type ClusterExtensionRevisionReconciler struct { + Client client.Client + RevisionEngine RevisionEngine + TrackingCache trackingCache +} + +type trackingCache interface { + client.Reader + Source(handler handler.EventHandler, predicates ...predicate.Predicate) source.Source + Watch(ctx context.Context, user client.Object, gvks sets.Set[schema.GroupVersionKind]) error + Free(ctx context.Context, user client.Object) error +} + +type RevisionEngine interface { + Teardown(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) + Reconcile(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) +} + +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensionrevisions,verbs=get;list;watch;update;patch;create;delete +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensionrevisions/status,verbs=update;patch +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensionrevisions/finalizers,verbs=update + +func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx).WithName("cluster-extension-revision") + ctx = log.IntoContext(ctx, l) + + existingRev := &ocv1.ClusterExtensionRevision{} + if err := c.Client.Get(ctx, req.NamespacedName, existingRev); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + l.Info("reconcile starting") + defer l.Info("reconcile ending") + + reconciledRev := existingRev.DeepCopy() + res, reconcileErr := c.reconcile(ctx, reconciledRev) + + // Do checks before any Update()s, as Update() may modify the resource structure! + updateStatus := !equality.Semantic.DeepEqual(existingRev.Status, reconciledRev.Status) + + unexpectedFieldsChanged := checkForUnexpectedClusterExtensionRevisionFieldChange(*existingRev, *reconciledRev) + if unexpectedFieldsChanged { + panic("spec or metadata changed by reconciler") + } + + // NOTE: finalizer updates are performed during c.reconcile as patches, so that reconcile can + // continue performing logic after successfully setting the finalizer. therefore we only need + // to set status here. + + if updateStatus { + if err := c.Client.Status().Update(ctx, reconciledRev); err != nil { + reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err)) + } + } + + return res, reconcileErr +} + +// Compare resources - ignoring status & metadata.finalizers +func checkForUnexpectedClusterExtensionRevisionFieldChange(a, b ocv1.ClusterExtensionRevision) bool { + a.Status, b.Status = ocv1.ClusterExtensionRevisionStatus{}, ocv1.ClusterExtensionRevisionStatus{} + + // when finalizers are updated during reconcile, we expect finalizers, managedFields, and resourceVersion + // to be updated, so we ignore changes in these fields. + a.Finalizers, b.Finalizers = []string{}, []string{} + a.ManagedFields, b.ManagedFields = nil, nil + a.ResourceVersion, b.ResourceVersion = "", "" + return !equality.Semantic.DeepEqual(a.Spec, b.Spec) +} + +func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev *ocv1.ClusterExtensionRevision) (ctrl.Result, error) { + l := log.FromContext(ctx) + + revision, opts, previous := toBoxcutterRevision(rev) + + if !rev.DeletionTimestamp.IsZero() || rev.Spec.LifecycleState == ocv1.ClusterExtensionRevisionLifecycleStateArchived { + return c.teardown(ctx, rev, revision) + } + + // + // Reconcile + // + if err := c.ensureFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer); err != nil { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonReconcileFailure, + Message: err.Error(), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, fmt.Errorf("error ensuring teardown finalizer: %v", err) + } + + if err := c.establishWatch(ctx, rev, revision); err != nil { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonReconcileFailure, + Message: err.Error(), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, fmt.Errorf("establish watch: %v", err) + } + + rres, err := c.RevisionEngine.Reconcile(ctx, *revision, opts...) + if err != nil { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonReconcileFailure, + Message: err.Error(), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, fmt.Errorf("revision reconcile: %v", err) + } + l.Info("reconcile report", "report", rres.String()) + + // Retry failing preflight checks with a flat 10s retry. + // TODO: report status, backoff? + if verr := rres.GetValidationError(); verr != nil { + l.Info("preflight error, retrying after 10s", "err", verr.String()) + + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonRevisionValidationFailure, + Message: fmt.Sprintf("revision validation error: %s", verr), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + for i, pres := range rres.GetPhases() { + if verr := pres.GetValidationError(); verr != nil { + l.Info("preflight error, retrying after 10s", "err", verr.String()) + + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonPhaseValidationError, + Message: fmt.Sprintf("phase %d validation error: %s", i, verr), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + var collidingObjs []string + for _, ores := range pres.GetObjects() { + if ores.Action() == machinery.ActionCollision { + collidingObjs = append(collidingObjs, ores.String()) + } + } + + if len(collidingObjs) > 0 { + l.Info("object collision error, retrying after 10s", "collisions", collidingObjs) + + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonObjectCollisions, + Message: fmt.Sprintf("revision object collisions in phase %d\n%s", i, strings.Join(collidingObjs, "\n\n")), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + } + + //nolint:nestif + if rres.IsComplete() { + // Archive other revisions. + for _, a := range previous { + patch := []byte(`{"spec":{"lifecycleState":"Archived"}}`) + if err := c.Client.Patch(ctx, a, client.RawPatch(types.MergePatchType, patch)); err != nil { + // TODO: It feels like an error here needs to propagate to a status _somewhere_. + // Not sure the current CER makes sense? But it also feels off to set the CE + // status from outside the CE reconciler. + return ctrl.Result{}, fmt.Errorf("archive previous Revision: %w", err) + } + } + + // Report status. + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionTrue, + Reason: ocv1.ClusterExtensionRevisionReasonAvailable, + Message: "Object is available and passes all probes.", + ObservedGeneration: rev.Generation, + }) + if !meta.IsStatusConditionTrue(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeSucceeded, + Status: metav1.ConditionTrue, + Reason: ocv1.ClusterExtensionRevisionReasonRolloutSuccess, + Message: "Revision succeeded rolling out.", + ObservedGeneration: rev.Generation, + }) + } + } else { + var probeFailureMsgs []string + for _, pres := range rres.GetPhases() { + if pres.IsComplete() { + continue + } + for _, ores := range pres.GetObjects() { + pr := ores.Probes()[boxcutter.ProgressProbeType] + if pr.Success { + continue + } + + obj := ores.Object() + gvk := obj.GetObjectKind().GroupVersionKind() + probeFailureMsgs = append(probeFailureMsgs, fmt.Sprintf( + "Object %s.%s %s/%s: %v", + gvk.Kind, gvk.GroupVersion().String(), + obj.GetNamespace(), obj.GetName(), strings.Join(pr.Messages, " and "), + )) + break + } + } + if len(probeFailureMsgs) > 0 { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonProbeFailure, + Message: strings.Join(probeFailureMsgs, "\n"), + ObservedGeneration: rev.Generation, + }) + } else { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonIncomplete, + Message: "Revision has not been rolled out completely.", + ObservedGeneration: rev.Generation, + }) + } + } + if rres.InTransistion() { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ClusterExtensionRevisionReasonProgressing, + Message: "Rollout in progress.", + ObservedGeneration: rev.Generation, + }) + } else { + meta.RemoveStatusCondition(&rev.Status.Conditions, ocv1.TypeProgressing) + } + + return ctrl.Result{}, nil +} + +func (c *ClusterExtensionRevisionReconciler) teardown(ctx context.Context, rev *ocv1.ClusterExtensionRevision, revision *boxcutter.Revision) (ctrl.Result, error) { + l := log.FromContext(ctx) + + tres, err := c.RevisionEngine.Teardown(ctx, *revision) + if err != nil { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonReconcileFailure, + Message: err.Error(), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, fmt.Errorf("revision teardown: %v", err) + } + + l.Info("teardown report", "report", tres.String()) + if !tres.IsComplete() { + // TODO: If it is not complete, it seems like it would be good to update + // the status in some way to tell the user that the teardown is still + // in progress. + return ctrl.Result{}, nil + } + + if err := c.TrackingCache.Free(ctx, rev); err != nil { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionFalse, + Reason: ocv1.ClusterExtensionRevisionReasonReconcileFailure, + Message: err.Error(), + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, fmt.Errorf("error stopping informers: %v", err) + } + + // Ensure Available condition is set to Unknown before removing the finalizer when archiving + if rev.Spec.LifecycleState == ocv1.ClusterExtensionRevisionLifecycleStateArchived && + !meta.IsStatusConditionPresentAndEqual(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable, metav1.ConditionUnknown) { + meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionUnknown, + Reason: ocv1.ClusterExtensionRevisionReasonArchived, + Message: "revision is archived", + ObservedGeneration: rev.Generation, + }) + return ctrl.Result{}, nil + } + + if err := c.removeFinalizer(ctx, rev, clusterExtensionRevisionTeardownFinalizer); err != nil { + return ctrl.Result{}, fmt.Errorf("error removing teardown finalizer: %v", err) + } + return ctrl.Result{}, nil +} + +type Sourcerer interface { + Source(handler handler.EventHandler, predicates ...predicate.Predicate) source.Source +} + +func (c *ClusterExtensionRevisionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For( + &ocv1.ClusterExtensionRevision{}, + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + WatchesRawSource( + c.TrackingCache.Source( + handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &ocv1.ClusterExtensionRevision{}), + predicate.ResourceVersionChangedPredicate{}, + ), + ). + Complete(c) +} + +func (c *ClusterExtensionRevisionReconciler) establishWatch( + ctx context.Context, rev *ocv1.ClusterExtensionRevision, + boxcutterRev *boxcutter.Revision, +) error { + gvks := sets.New[schema.GroupVersionKind]() + for _, phase := range boxcutterRev.Phases { + for _, obj := range phase.Objects { + gvks.Insert(obj.GroupVersionKind()) + } + } + + return c.TrackingCache.Watch(ctx, rev, gvks) +} + +func (c *ClusterExtensionRevisionReconciler) ensureFinalizer( + ctx context.Context, obj client.Object, finalizer string, +) error { + if controllerutil.ContainsFinalizer(obj, finalizer) { + return nil + } + + controllerutil.AddFinalizer(obj, finalizer) + patch := map[string]any{ + "metadata": map[string]any{ + "resourceVersion": obj.GetResourceVersion(), + "finalizers": obj.GetFinalizers(), + }, + } + patchJSON, err := json.Marshal(patch) + if err != nil { + return fmt.Errorf("marshalling patch to remove finalizer: %w", err) + } + if err := c.Client.Patch(ctx, obj, client.RawPatch(types.MergePatchType, patchJSON)); err != nil { + return fmt.Errorf("adding finalizer: %w", err) + } + return nil +} + +func (c *ClusterExtensionRevisionReconciler) removeFinalizer(ctx context.Context, obj client.Object, finalizer string) error { + if !controllerutil.ContainsFinalizer(obj, finalizer) { + return nil + } + + controllerutil.RemoveFinalizer(obj, finalizer) + + patch := map[string]any{ + "metadata": map[string]any{ + "resourceVersion": obj.GetResourceVersion(), + "finalizers": obj.GetFinalizers(), + }, + } + patchJSON, err := json.Marshal(patch) + if err != nil { + return fmt.Errorf("marshalling patch to remove finalizer: %w", err) + } + if err := c.Client.Patch(ctx, obj, client.RawPatch(types.MergePatchType, patchJSON)); err != nil { + return fmt.Errorf("removing finalizer: %w", err) + } + return nil +} + +func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revision, []boxcutter.RevisionReconcileOption, []client.Object) { + previous := make([]client.Object, 0, len(rev.Spec.Previous)) + for _, specPrevious := range rev.Spec.Previous { + prev := &unstructured.Unstructured{} + prev.SetName(specPrevious.Name) + prev.SetUID(specPrevious.UID) + prev.SetGroupVersionKind(ocv1.GroupVersion.WithKind(ocv1.ClusterExtensionRevisionKind)) + previous = append(previous, prev) + } + + opts := []boxcutter.RevisionReconcileOption{ + boxcutter.WithPreviousOwners(previous), + boxcutter.WithProbe(boxcutter.ProgressProbeType, probing.And{ + deploymentProbe, statefulSetProbe, crdProbe, issuerProbe, certProbe, + }), + } + + r := &boxcutter.Revision{ + Name: rev.Name, + Owner: rev, + Revision: rev.Spec.Revision, + } + for _, specPhase := range rev.Spec.Phases { + phase := boxcutter.Phase{Name: specPhase.Name} + for _, specObj := range specPhase.Objects { + obj := specObj.Object.DeepCopy() + + labels := obj.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[ClusterExtensionRevisionOwnerLabel] = rev.Labels[ClusterExtensionRevisionOwnerLabel] + obj.SetLabels(labels) + + switch specObj.CollisionProtection { + case ocv1.CollisionProtectionIfNoController, ocv1.CollisionProtectionNone: + opts = append(opts, boxcutter.WithObjectReconcileOptions( + obj, boxcutter.WithCollisionProtection(specObj.CollisionProtection))) + } + + phase.Objects = append(phase.Objects, *obj) + } + r.Phases = append(r.Phases, phase) + } + + if rev.Spec.LifecycleState == ocv1.ClusterExtensionRevisionLifecycleStatePaused { + opts = append(opts, boxcutter.WithPaused{}) + } + return r, opts, previous +} + +var ( + deploymentProbe = &probing.GroupKindSelector{ + GroupKind: schema.GroupKind{Group: appsv1.GroupName, Kind: "Deployment"}, + Prober: deplStatefulSetProbe, + } + statefulSetProbe = &probing.GroupKindSelector{ + GroupKind: schema.GroupKind{Group: appsv1.GroupName, Kind: "StatefulSet"}, + Prober: deplStatefulSetProbe, + } + crdProbe = &probing.GroupKindSelector{ + GroupKind: schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}, + Prober: &probing.ObservedGenerationProbe{ + Prober: &probing.ConditionProbe{ // "Available" == "True" + Type: string(apiextensions.Established), + Status: string(corev1.ConditionTrue), + }, + }, + } + certProbe = &probing.GroupKindSelector{ + GroupKind: schema.GroupKind{Group: "acme.cert-manager.io", Kind: "Certificate"}, + Prober: &probing.ObservedGenerationProbe{ + Prober: readyConditionProbe, + }, + } + issuerProbe = &probing.GroupKindSelector{ + GroupKind: schema.GroupKind{Group: "acme.cert-manager.io", Kind: "Issuer"}, + Prober: &probing.ObservedGenerationProbe{ + Prober: readyConditionProbe, + }, + } + + // deplStaefulSetProbe probes Deployment, StatefulSet objects. + deplStatefulSetProbe = &probing.ObservedGenerationProbe{ + Prober: probing.And{ + availableConditionProbe, + replicasUpdatedProbe, + }, + } + + // Checks if the Type: "Available" Condition is "True". + availableConditionProbe = &probing.ConditionProbe{ // "Available" == "True" + Type: string(appsv1.DeploymentAvailable), + Status: string(corev1.ConditionTrue), + } + + // Checks if the Type: "Ready" Condition is "True" + readyConditionProbe = &probing.ObservedGenerationProbe{ + Prober: &probing.ConditionProbe{ + Type: "Ready", + Status: "True", + }, + } + + // Checks if .status.updatedReplicas == .status.replicas. + // Works for StatefulSts, Deployments and ReplicaSets. + replicasUpdatedProbe = &probing.FieldsEqualProbe{ + FieldA: ".status.updatedReplicas", + FieldB: ".status.replicas", + } +) diff --git a/internal/operator-controller/controllers/clusterextensionrevision_controller_test.go b/internal/operator-controller/controllers/clusterextensionrevision_controller_test.go new file mode 100644 index 0000000000..873a6cc748 --- /dev/null +++ b/internal/operator-controller/controllers/clusterextensionrevision_controller_test.go @@ -0,0 +1,902 @@ +package controllers_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "pkg.package-operator.run/boxcutter" + "pkg.package-operator.run/boxcutter/machinery" + machinerytypes "pkg.package-operator.run/boxcutter/machinery/types" + "pkg.package-operator.run/boxcutter/validation" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" +) + +func Test_ClusterExtensionRevisionReconciler_Reconcile_RevisionProgression(t *testing.T) { + const ( + clusterExtensionRevisionName = "test-ext-1" + ) + + testScheme := newScheme(t) + + for _, tc := range []struct { + name string + existingObjs func() []client.Object + revisionResult machinery.RevisionResult + validate func(*testing.T, client.Client) + }{ + { + name: "sets teardown finalizer", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + require.Contains(t, rev.Finalizers, "olm.operatorframework.io/teardown") + }, + }, + { + name: "set Available:False:InComplete status condition during rollout when no probe failures are detected", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonIncomplete, cond.Reason) + require.Equal(t, "Revision has not been rolled out completely.", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + }, + }, + { + name: "set Available:False:ProbeFailure condition when probe failures are detected", + revisionResult: mockRevisionResult{ + phases: []machinery.PhaseResult{ + mockPhaseResult{ + name: "somephase", + isComplete: false, + objects: []machinery.ObjectResult{ + mockObjectResult{ + success: true, + probes: map[string]machinery.ObjectProbeResult{ + boxcutter.ProgressProbeType: { + Success: true, + }, + }, + }, + mockObjectResult{ + success: false, + object: func() client.Object { + obj := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + Namespace: "my-namespace", + }, + } + obj.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Service")) + return obj + }(), + probes: map[string]machinery.ObjectProbeResult{ + boxcutter.ProgressProbeType: { + Success: false, + Messages: []string{ + "something bad happened", + "something worse happened", + }, + }, + }, + }, + }, + }, + mockPhaseResult{ + name: "someotherphase", + isComplete: false, + objects: []machinery.ObjectResult{ + mockObjectResult{ + success: false, + object: func() client.Object { + obj := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-configmap", + Namespace: "my-namespace", + }, + } + obj.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap")) + return obj + }(), + probes: map[string]machinery.ObjectProbeResult{ + boxcutter.ProgressProbeType: { + Success: false, + Messages: []string{ + "we have a problem", + }, + }, + }, + }, + }, + }, + }, + }, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonProbeFailure, cond.Reason) + require.Equal(t, "Object Service.v1 my-namespace/my-service: something bad happened and something worse happened\nObject ConfigMap.v1 my-namespace/my-configmap: we have a problem", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + }, + }, + { + name: "set Progressing:True:Progressing condition while revision is transitioning", + revisionResult: mockRevisionResult{ + inTransition: true, + }, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonProgressing, cond.Reason) + require.Equal(t, "Rollout in progress.", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + }, + }, + { + name: "remove Progressing condition once transition rollout is finished", + revisionResult: mockRevisionResult{ + inTransition: false, + }, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + meta.SetStatusCondition(&rev1.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ClusterExtensionRevisionReasonProgressing, + Message: "some message", + ObservedGeneration: 1, + }) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.TypeProgressing) + require.Nil(t, cond) + }, + }, + { + name: "set Available:True:Available and Succeeded:True:RolloutSuccess conditions on successful revision rollout", + revisionResult: mockRevisionResult{ + isComplete: true, + }, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonAvailable, cond.Reason) + require.Equal(t, "Object is available and passes all probes.", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + + cond = meta.FindStatusCondition(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonRolloutSuccess, cond.Reason) + require.Equal(t, "Revision succeeded rolling out.", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + }, + }, + { + name: "archive previous revisions on successful rollout", + revisionResult: mockRevisionResult{ + isComplete: true, + }, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + prevRev1 := newTestClusterExtensionRevision("prev-rev-1") + require.NoError(t, controllerutil.SetControllerReference(ext, prevRev1, testScheme)) + prevRev2 := newTestClusterExtensionRevision("prev-rev-2") + require.NoError(t, controllerutil.SetControllerReference(ext, prevRev2, testScheme)) + rev1 := newTestClusterExtensionRevision("test-ext-1") + rev1.Spec.Previous = []ocv1.ClusterExtensionRevisionPrevious{ + { + Name: "prev-rev-1", + UID: "prev-rev-1", + }, { + Name: "prev-rev-2", + UID: "prev-rev-2", + }, + } + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{ext, prevRev1, prevRev2, rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: "prev-rev-1", + }, rev) + require.NoError(t, err) + require.Equal(t, ocv1.ClusterExtensionRevisionLifecycleStateArchived, rev.Spec.LifecycleState) + + err = c.Get(t.Context(), client.ObjectKey{ + Name: "prev-rev-2", + }, rev) + require.NoError(t, err) + require.Equal(t, ocv1.ClusterExtensionRevisionLifecycleStateArchived, rev.Spec.LifecycleState) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + // create extension and cluster extension + testClient := fake.NewClientBuilder(). + WithScheme(testScheme). + WithStatusSubresource(&ocv1.ClusterExtensionRevision{}). + WithObjects(tc.existingObjs()...). + Build() + + // reconcile cluster extension revision + result, err := (&controllers.ClusterExtensionRevisionReconciler{ + Client: testClient, + RevisionEngine: &mockRevisionEngine{ + reconcile: func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) { + return tc.revisionResult, nil + }, + }, + TrackingCache: &mockTrackingCache{}, + }).Reconcile(t.Context(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: clusterExtensionRevisionName, + }, + }) + + // reconcile cluster extensionr evision + require.Equal(t, ctrl.Result{}, result) + require.NoError(t, err) + + // validate test case + tc.validate(t, testClient) + }) + } +} + +func Test_ClusterExtensionRevisionReconciler_Reconcile_ValidationError_Retries(t *testing.T) { + const ( + clusterExtensionName = "test-ext" + clusterExtensionRevisionName = "test-ext-1" + ) + + testScheme := newScheme(t) + + for _, tc := range []struct { + name string + revisionResult machinery.RevisionResult + }{ + { + name: "retries on revision result validation error", + revisionResult: mockRevisionResult{ + validationError: &validation.RevisionValidationError{ + RevisionName: "test-ext-1", + RevisionNumber: 1, + Phases: []validation.PhaseValidationError{ + { + PhaseName: "everything", + PhaseError: fmt.Errorf("some error"), + Objects: []validation.ObjectValidationError{ + { + ObjectRef: machinerytypes.ObjectRef{ + GroupVersionKind: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "ConfigMap", + }, + ObjectKey: client.ObjectKey{ + Name: "my-configmap", + Namespace: "my-namespace", + }, + }, + Errors: []error{ + fmt.Errorf("is not a config"), + fmt.Errorf("is not a map"), + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "retries on revision result phase validation error", + revisionResult: mockRevisionResult{ + phases: []machinery.PhaseResult{ + mockPhaseResult{ + validationError: &validation.PhaseValidationError{ + PhaseName: "everything", + PhaseError: fmt.Errorf("some error"), + Objects: []validation.ObjectValidationError{ + { + ObjectRef: machinerytypes.ObjectRef{ + GroupVersionKind: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "ConfigMap", + }, + ObjectKey: client.ObjectKey{ + Name: "my-configmap", + Namespace: "my-namespace", + }, + }, + Errors: []error{ + fmt.Errorf("is not a config"), + fmt.Errorf("is not a map"), + }, + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + + // create extension and cluster extension + testClient := fake.NewClientBuilder(). + WithScheme(testScheme). + WithStatusSubresource(&ocv1.ClusterExtensionRevision{}). + WithObjects(ext, rev1). + Build() + + // reconcile cluster extension revision + result, err := (&controllers.ClusterExtensionRevisionReconciler{ + Client: testClient, + RevisionEngine: &mockRevisionEngine{ + reconcile: func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) { + return tc.revisionResult, nil + }, + }, + TrackingCache: &mockTrackingCache{}, + }).Reconcile(t.Context(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: clusterExtensionRevisionName, + }, + }) + + // reconcile cluster extensionr evision + require.Equal(t, ctrl.Result{ + RequeueAfter: 10 * time.Second, + }, result) + require.NoError(t, err) + }) + } +} + +func Test_ClusterExtensionRevisionReconciler_Reconcile_Deletion(t *testing.T) { + const ( + clusterExtensionRevisionName = "test-ext-1" + ) + + testScheme := newScheme(t) + require.NoError(t, corev1.AddToScheme(testScheme)) + + for _, tc := range []struct { + name string + existingObjs func() []client.Object + revisionResult machinery.RevisionResult + revisionEngineTeardownFn func(*testing.T) func(context.Context, machinerytypes.Revision, ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) + validate func(*testing.T, client.Client) + expectedErr string + }{ + { + name: "teardown finalizer is removed", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + return []client.Object{rev1} + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + require.NotContains(t, "olm.operatorframework.io/teardown", rev.Finalizers) + }, + revisionEngineTeardownFn: func(t *testing.T) func(context.Context, machinerytypes.Revision, ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return nil + }, + }, + { + name: "revision is torn down and deleted when deleted", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + rev1.DeletionTimestamp = &metav1.Time{Time: time.Now()} + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{rev1, ext} + }, + revisionEngineTeardownFn: func(t *testing.T) func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return &mockRevisionTeardownResult{ + isComplete: true, + }, nil + } + }, + validate: func(t *testing.T, c client.Client) { + t.Log("cluster revision is deleted") + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.Error(t, err) + require.True(t, errors.IsNotFound(err)) + }, + }, + { + name: "surfaces tear down errors when deleted", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + rev1.DeletionTimestamp = &metav1.Time{Time: time.Now()} + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{rev1, ext} + }, + revisionEngineTeardownFn: func(t *testing.T) func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return nil, fmt.Errorf("some teardown error") + } + }, + expectedErr: "some teardown error", + validate: func(t *testing.T, c client.Client) { + t.Log("cluster revision is not deleted and still contains finalizer") + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + require.NotContains(t, "olm.operatorframework.io/teardown", rev.Finalizers) + }, + }, + { + name: "set Available condition to Unknown with reason Archived when archiving revision", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + rev1.Spec.LifecycleState = ocv1.ClusterExtensionRevisionLifecycleStateArchived + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{rev1, ext} + }, + revisionEngineTeardownFn: func(t *testing.T) func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return &mockRevisionTeardownResult{ + isComplete: true, + }, nil + } + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + cond := meta.FindStatusCondition(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, ocv1.ClusterExtensionRevisionReasonArchived, cond.Reason) + require.Equal(t, "revision is archived", cond.Message) + require.Equal(t, int64(1), cond.ObservedGeneration) + }, + }, + { + name: "revision is torn down when in archived state and finalizer is removed", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + rev1.Spec.LifecycleState = ocv1.ClusterExtensionRevisionLifecycleStateArchived + meta.SetStatusCondition(&rev1.Status.Conditions, metav1.Condition{ + Type: ocv1.ClusterExtensionRevisionTypeAvailable, + Status: metav1.ConditionUnknown, + Reason: ocv1.ClusterExtensionRevisionReasonArchived, + Message: "revision is archived", + ObservedGeneration: rev1.Generation, + }) + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{rev1, ext} + }, + revisionEngineTeardownFn: func(t *testing.T) func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return &mockRevisionTeardownResult{ + isComplete: true, + }, nil + } + }, + validate: func(t *testing.T, c client.Client) { + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + require.NotContains(t, rev.Finalizers, "olm.operatorframework.io/teardown") + }, + }, + { + name: "surfaces revision teardown error when in archived state", + revisionResult: mockRevisionResult{}, + existingObjs: func() []client.Object { + ext := newTestClusterExtension() + rev1 := newTestClusterExtensionRevision(clusterExtensionRevisionName) + rev1.Finalizers = []string{ + "olm.operatorframework.io/teardown", + } + rev1.Spec.LifecycleState = ocv1.ClusterExtensionRevisionLifecycleStateArchived + require.NoError(t, controllerutil.SetControllerReference(ext, rev1, testScheme)) + return []client.Object{rev1, ext} + }, + revisionEngineTeardownFn: func(t *testing.T) func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return nil, fmt.Errorf("some teardown error") + } + }, + expectedErr: "some teardown error", + validate: func(t *testing.T, c client.Client) { + t.Log("cluster revision is not deleted and still contains finalizer") + rev := &ocv1.ClusterExtensionRevision{} + err := c.Get(t.Context(), client.ObjectKey{ + Name: clusterExtensionRevisionName, + }, rev) + require.NoError(t, err) + require.NotContains(t, "olm.operatorframework.io/teardown", rev.Finalizers) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + // create extension and cluster extension + testClient := fake.NewClientBuilder(). + WithScheme(testScheme). + WithStatusSubresource(&ocv1.ClusterExtensionRevision{}). + WithObjects(tc.existingObjs()...). + Build() + + // reconcile cluster extension revision + result, err := (&controllers.ClusterExtensionRevisionReconciler{ + Client: testClient, + RevisionEngine: &mockRevisionEngine{ + reconcile: func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) { + return tc.revisionResult, nil + }, + teardown: tc.revisionEngineTeardownFn(t), + }, + TrackingCache: &mockTrackingCache{}, + }).Reconcile(t.Context(), ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: clusterExtensionRevisionName, + }, + }) + + // reconcile cluster extension revision + require.Equal(t, ctrl.Result{}, result) + if tc.expectedErr != "" { + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + } + + // validate test case + tc.validate(t, testClient) + }) + } +} + +func newTestClusterExtension() *ocv1.ClusterExtension { + return &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ext", + UID: "test-ext", + }, + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "some-namespace", + ServiceAccount: ocv1.ServiceAccountReference{ + Name: "service-account", + }, + Source: ocv1.SourceConfig{ + SourceType: ocv1.SourceTypeCatalog, + Catalog: &ocv1.CatalogFilter{ + PackageName: "some-package", + }, + }, + }, + } +} + +func newTestClusterExtensionRevision(name string) *ocv1.ClusterExtensionRevision { + return &ocv1.ClusterExtensionRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + UID: types.UID(name), + Generation: int64(1), + }, + Spec: ocv1.ClusterExtensionRevisionSpec{ + Phases: []ocv1.ClusterExtensionRevisionPhase{ + { + Name: "everything", + Objects: []ocv1.ClusterExtensionRevisionObject{ + { + Object: unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "data": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +type mockRevisionEngine struct { + teardown func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) + reconcile func(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) +} + +func (m mockRevisionEngine) Teardown(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionTeardownOption) (machinery.RevisionTeardownResult, error) { + return m.teardown(ctx, rev) +} + +func (m mockRevisionEngine) Reconcile(ctx context.Context, rev machinerytypes.Revision, opts ...machinerytypes.RevisionReconcileOption) (machinery.RevisionResult, error) { + return m.reconcile(ctx, rev) +} + +type mockRevisionResult struct { + validationError *validation.RevisionValidationError + phases []machinery.PhaseResult + inTransition bool + isComplete bool + hasProgressed bool + string string +} + +func (m mockRevisionResult) GetValidationError() *validation.RevisionValidationError { + return m.validationError +} + +func (m mockRevisionResult) GetPhases() []machinery.PhaseResult { + return m.phases +} + +func (m mockRevisionResult) InTransistion() bool { + return m.inTransition +} + +func (m mockRevisionResult) IsComplete() bool { + return m.isComplete +} + +func (m mockRevisionResult) HasProgressed() bool { + return m.hasProgressed +} + +func (m mockRevisionResult) String() string { + return m.string +} + +type mockPhaseResult struct { + name string + validationError *validation.PhaseValidationError + objects []machinery.ObjectResult + inTransition bool + isComplete bool + hasProgressed bool + string string +} + +func (m mockPhaseResult) GetName() string { + return m.name +} + +func (m mockPhaseResult) GetValidationError() *validation.PhaseValidationError { + return m.validationError +} + +func (m mockPhaseResult) GetObjects() []machinery.ObjectResult { + return m.objects +} + +func (m mockPhaseResult) InTransistion() bool { + return m.inTransition +} + +func (m mockPhaseResult) IsComplete() bool { + return m.isComplete +} + +func (m mockPhaseResult) HasProgressed() bool { + return m.hasProgressed +} + +func (m mockPhaseResult) String() string { + return m.string +} + +type mockObjectResult struct { + action machinery.Action + object machinery.Object + success bool + probes map[string]machinery.ObjectProbeResult + string string +} + +func (m mockObjectResult) Action() machinery.Action { + return m.action +} + +func (m mockObjectResult) Object() machinery.Object { + return m.object +} + +func (m mockObjectResult) Success() bool { + return m.success +} + +func (m mockObjectResult) Probes() map[string]machinery.ObjectProbeResult { + return m.probes +} + +func (m mockObjectResult) String() string { + return m.string +} + +type mockRevisionTeardownResult struct { + phases []machinery.PhaseTeardownResult + isComplete bool + waitingPhaseNames []string + activePhaseName string + phaseIsActive bool + gonePhaseNames []string + string string +} + +func (m mockRevisionTeardownResult) GetPhases() []machinery.PhaseTeardownResult { + return m.phases +} + +func (m mockRevisionTeardownResult) IsComplete() bool { + return m.isComplete +} + +func (m mockRevisionTeardownResult) GetWaitingPhaseNames() []string { + return m.waitingPhaseNames +} + +func (m mockRevisionTeardownResult) GetActivePhaseName() (string, bool) { + return m.activePhaseName, m.phaseIsActive +} + +func (m mockRevisionTeardownResult) GetGonePhaseNames() []string { + return m.gonePhaseNames +} + +func (m mockRevisionTeardownResult) String() string { + return m.string +} + +type mockTrackingCache struct{} + +func (m *mockTrackingCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + panic("not implemented") +} + +func (m *mockTrackingCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("not implemented") +} + +func (m *mockTrackingCache) Source(handler handler.EventHandler, predicates ...predicate.Predicate) source.Source { + panic("not implemented") +} + +func (m *mockTrackingCache) Watch(ctx context.Context, user client.Object, gvks sets.Set[schema.GroupVersionKind]) error { + return nil +} + +func (m *mockTrackingCache) Free(ctx context.Context, user client.Object) error { + return nil +} diff --git a/internal/operator-controller/controllers/common_controller.go b/internal/operator-controller/controllers/common_controller.go new file mode 100644 index 0000000000..7fafc7bb7b --- /dev/null +++ b/internal/operator-controller/controllers/common_controller.go @@ -0,0 +1,131 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "errors" + "fmt" + + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +const ( + // maxConditionMessageLength set the max message length allowed by Kubernetes. + maxConditionMessageLength = 32768 + // truncationSuffix is the suffix added when a message is cut. + truncationSuffix = "\n\n... [message truncated]" +) + +// truncateMessage cuts long messages to fit Kubernetes condition limits +func truncateMessage(message string) string { + if len(message) <= maxConditionMessageLength { + return message + } + + maxContent := maxConditionMessageLength - len(truncationSuffix) + return message[:maxContent] + truncationSuffix +} + +// SetStatusCondition wraps apimeta.SetStatusCondition and ensures the message is always truncated +// This should be used throughout the codebase instead of apimeta.SetStatusCondition directly +func SetStatusCondition(conditions *[]metav1.Condition, condition metav1.Condition) { + condition.Message = truncateMessage(condition.Message) + apimeta.SetStatusCondition(conditions, condition) +} + +// setInstalledStatusFromRevisionStates sets the installed status based on the given installedBundle. +func setInstalledStatusFromRevisionStates(ext *ocv1.ClusterExtension, revisionStates *RevisionStates) { + // Nothing is installed + if revisionStates.Installed == nil { + setInstallStatus(ext, nil) + if len(revisionStates.RollingOut) == 0 { + setInstalledStatusConditionFalse(ext, ocv1.ReasonFailed, "No bundle installed") + } else { + setInstalledStatusConditionFalse(ext, ocv1.ReasonAbsent, "No bundle installed") + } + return + } + // Something is installed + installStatus := &ocv1.ClusterExtensionInstallStatus{ + Bundle: revisionStates.Installed.BundleMetadata, + } + setInstallStatus(ext, installStatus) + setInstalledStatusConditionSuccess(ext, fmt.Sprintf("Installed bundle %s successfully", revisionStates.Installed.Image)) +} + +// setInstalledStatusConditionSuccess sets the installed status condition to success. +func setInstalledStatusConditionSuccess(ext *ocv1.ClusterExtension, message string) { + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeInstalled, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: message, + ObservedGeneration: ext.GetGeneration(), + }) +} + +// setInstalledStatusConditionFailed sets the installed status condition to failed. +func setInstalledStatusConditionFalse(ext *ocv1.ClusterExtension, reason string, message string) { + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeInstalled, + Status: metav1.ConditionFalse, + Reason: reason, + Message: message, + ObservedGeneration: ext.GetGeneration(), + }) +} + +// setInstalledStatusConditionUnknown sets the installed status condition to unknown. +func setInstalledStatusConditionUnknown(ext *ocv1.ClusterExtension, message string) { + SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1.TypeInstalled, + Status: metav1.ConditionUnknown, + Reason: ocv1.ReasonFailed, + Message: message, + ObservedGeneration: ext.GetGeneration(), + }) +} + +func setInstallStatus(ext *ocv1.ClusterExtension, installStatus *ocv1.ClusterExtensionInstallStatus) { + ext.Status.Install = installStatus +} + +func setStatusProgressing(ext *ocv1.ClusterExtension, err error) { + progressingCond := metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: "Desired state reached", + ObservedGeneration: ext.GetGeneration(), + } + + if err != nil { + progressingCond.Reason = ocv1.ReasonRetrying + progressingCond.Message = err.Error() + } + + if errors.Is(err, reconcile.TerminalError(nil)) { + progressingCond.Status = metav1.ConditionFalse + progressingCond.Reason = ocv1.ReasonBlocked + } + + SetStatusCondition(&ext.Status.Conditions, progressingCond) +} diff --git a/internal/operator-controller/controllers/common_controller_test.go b/internal/operator-controller/controllers/common_controller_test.go new file mode 100644 index 0000000000..4d0a0536d1 --- /dev/null +++ b/internal/operator-controller/controllers/common_controller_test.go @@ -0,0 +1,242 @@ +package controllers + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +func TestSetStatusProgressing(t *testing.T) { + for _, tc := range []struct { + name string + err error + clusterExtension *ocv1.ClusterExtension + expected metav1.Condition + }{ + { + name: "non-nil ClusterExtension, nil error, Progressing condition has status True with reason Success", + err: nil, + clusterExtension: &ocv1.ClusterExtension{}, + expected: metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonSucceeded, + Message: "Desired state reached", + }, + }, + { + name: "non-nil ClusterExtension, non-terminal error, Progressing condition has status True with reason Retrying", + err: errors.New("boom"), + clusterExtension: &ocv1.ClusterExtension{}, + expected: metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionTrue, + Reason: ocv1.ReasonRetrying, + Message: "boom", + }, + }, + { + name: "non-nil ClusterExtension, terminal error, Progressing condition has status False with reason Blocked", + err: reconcile.TerminalError(errors.New("boom")), + clusterExtension: &ocv1.ClusterExtension{}, + expected: metav1.Condition{ + Type: ocv1.TypeProgressing, + Status: metav1.ConditionFalse, + Reason: ocv1.ReasonBlocked, + Message: "terminal error: boom", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + setStatusProgressing(tc.clusterExtension, tc.err) + progressingCond := meta.FindStatusCondition(tc.clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, progressingCond, "progressing condition should be set but was not") + diff := cmp.Diff(*progressingCond, tc.expected, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime", "ObservedGeneration")) + require.Empty(t, diff, "difference between actual and expected Progressing conditions") + }) + } +} + +func TestTruncateMessage(t *testing.T) { + tests := []struct { + name string + message string + expected string + }{ + { + name: "short message unchanged", + message: "This is a short message", + expected: "This is a short message", + }, + { + name: "empty message unchanged", + message: "", + expected: "", + }, + { + name: "exact max length message unchanged", + message: strings.Repeat("a", maxConditionMessageLength), + expected: strings.Repeat("a", maxConditionMessageLength), + }, + { + name: "message just over limit gets truncated", + message: strings.Repeat("a", maxConditionMessageLength+1), + expected: strings.Repeat("a", maxConditionMessageLength-len(truncationSuffix)) + truncationSuffix, + }, + { + name: "very long message gets truncated", + message: strings.Repeat("word ", 10000) + "finalword", + expected: strings.Repeat("word ", 10000)[:maxConditionMessageLength-len(truncationSuffix)] + truncationSuffix, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := truncateMessage(tc.message) + require.Equal(t, tc.expected, result) + + // Verify the result is within the limit + require.LessOrEqual(t, len(result), maxConditionMessageLength, + "truncated message should not exceed max length") + + // If the original message was over the limit, verify truncation occurred + if len(tc.message) > maxConditionMessageLength { + require.Contains(t, result, truncationSuffix, + "long messages should contain truncation suffix") + require.Less(t, len(result), len(tc.message), + "truncated message should be shorter than original") + } + }) + } +} + +func TestSetStatusProgressingWithLongMessage(t *testing.T) { + // Simulate a real ClusterExtension CRD upgrade safety check failure with many validation errors + longError := fmt.Sprintf("validating CRD upgrade safety for ClusterExtension 'my-operator': %s", + strings.Repeat("CRD \"myresources.example.com\" v1beta1->v1: field .spec.replicas changed from optional to required, field .spec.config.timeout type changed from string to integer, field .status.conditions[].observedGeneration removed\n", 500)) + + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "my-operator"}} + err := errors.New(longError) + setStatusProgressing(ext, err) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(t, cond) + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix) + require.Contains(t, cond.Message, "validating CRD upgrade safety") +} + +func TestClusterExtensionDeprecationMessageTruncation(t *testing.T) { + // Test truncation for ClusterExtension deprecation warnings with many deprecated APIs + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "legacy-operator"}} + + // Simulate many deprecation warnings that would overflow the message limit + deprecationMessages := []string{} + for i := 0; i < 1000; i++ { + deprecationMessages = append(deprecationMessages, fmt.Sprintf("API version 'v1beta1' of resource 'customresources%d.example.com' is deprecated, use 'v1' instead", i)) + } + + longDeprecationMsg := strings.Join(deprecationMessages, "; ") + setInstalledStatusConditionUnknown(ext, longDeprecationMsg) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, cond) + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix, "deprecation messages should be truncated when too long") + require.Contains(t, cond.Message, "API version", "should preserve important deprecation context") +} + +func TestClusterExtensionInstallationFailureTruncation(t *testing.T) { + // Test truncation for ClusterExtension installation failures with many bundle validation errors + installError := "failed to install ClusterExtension 'argocd-operator': bundle validation errors: " + + strings.Repeat("resource 'deployments/argocd-server' missing required label 'app.kubernetes.io/name', resource 'services/argocd-server-metrics' has invalid port configuration, resource 'configmaps/argocd-cm' contains invalid YAML in data field 'application.yaml'\n", 400) + + ext := &ocv1.ClusterExtension{ObjectMeta: metav1.ObjectMeta{Name: "argocd-operator"}} + setInstalledStatusConditionFalse(ext, ocv1.ReasonFailed, installError) + + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(t, cond) + + // Verify message was truncated due to length + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength) + require.Contains(t, cond.Message, truncationSuffix, "installation failure messages should be truncated when too long") + require.Contains(t, cond.Message, "failed to install ClusterExtension", "should preserve important context") + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, ocv1.ReasonFailed, cond.Reason) + + // Verify original message was actually longer than the limit + require.Greater(t, len(installError), maxConditionMessageLength, "test should use a message that exceeds the limit") +} + +func TestSetStatusConditionWrapper(t *testing.T) { + tests := []struct { + name string + message string + expectedTruncated bool + }{ + { + name: "short message not truncated", + message: "This is a short message", + expectedTruncated: false, + }, + { + name: "long message gets truncated", + message: strings.Repeat("This is a very long message. ", 2000), + expectedTruncated: true, + }, + { + name: "message at exact limit not truncated", + message: strings.Repeat("a", maxConditionMessageLength), + expectedTruncated: false, + }, + { + name: "message over limit gets truncated", + message: strings.Repeat("a", maxConditionMessageLength+1), + expectedTruncated: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var conditions []metav1.Condition + + // Use our wrapper function + SetStatusCondition(&conditions, metav1.Condition{ + Type: "TestCondition", + Status: metav1.ConditionTrue, + Reason: "Testing", + Message: tc.message, + }) + + require.Len(t, conditions, 1, "should have exactly one condition") + cond := conditions[0] + + // Verify message is within limits + require.LessOrEqual(t, len(cond.Message), maxConditionMessageLength, + "condition message should not exceed max length") + + // Check if truncation occurred as expected + if tc.expectedTruncated { + require.Contains(t, cond.Message, truncationSuffix, + "long messages should contain truncation suffix") + require.Less(t, len(cond.Message), len(tc.message), + "truncated message should be shorter than original") + } else { + require.Equal(t, tc.message, cond.Message, + "short messages should remain unchanged") + require.NotContains(t, cond.Message, truncationSuffix, + "short messages should not contain truncation suffix") + } + }) + } +} diff --git a/internal/operator-controller/controllers/suite_test.go b/internal/operator-controller/controllers/suite_test.go new file mode 100644 index 0000000000..02d5382371 --- /dev/null +++ b/internal/operator-controller/controllers/suite_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers_test + +import ( + "context" + "io/fs" + "log" + "os" + "testing" + + "github.com/stretchr/testify/require" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/controllers" + "github.com/operator-framework/operator-controller/test" +) + +func newScheme(t *testing.T) *apimachineryruntime.Scheme { + sch := apimachineryruntime.NewScheme() + require.NoError(t, ocv1.AddToScheme(sch)) + return sch +} + +func newClient(t *testing.T) client.Client { + // TODO: this is a live client, which behaves differently than a cache client. + // We may want to use a caching client instead to get closer to real behavior. + cl, err := client.New(config, client.Options{Scheme: newScheme(t)}) + require.NoError(t, err) + require.NotNil(t, cl) + return cl +} + +var _ controllers.RevisionStatesGetter = (*MockRevisionStatesGetter)(nil) + +type MockRevisionStatesGetter struct { + *controllers.RevisionStates + Err error +} + +func (m *MockRevisionStatesGetter) GetRevisionStates(ctx context.Context, ext *ocv1.ClusterExtension) (*controllers.RevisionStates, error) { + if m.Err != nil { + return nil, m.Err + } + return m.RevisionStates, nil +} + +var _ controllers.Applier = (*MockApplier)(nil) + +type MockApplier struct { + installCompleted bool + installStatus string + err error +} + +func (m *MockApplier) Apply(_ context.Context, _ fs.FS, _ *ocv1.ClusterExtension, _ map[string]string, _ map[string]string) (bool, string, error) { + return m.installCompleted, m.installStatus, m.err +} + +func newClientAndReconciler(t *testing.T) (client.Client, *controllers.ClusterExtensionReconciler) { + cl := newClient(t) + + reconciler := &controllers.ClusterExtensionReconciler{ + Client: cl, + RevisionStatesGetter: &MockRevisionStatesGetter{ + RevisionStates: &controllers.RevisionStates{}, + }, + Finalizers: crfinalizer.NewFinalizers(), + } + return cl, reconciler +} + +var config *rest.Config + +func TestMain(m *testing.M) { + testEnv := test.NewEnv() + + var err error + config, err = testEnv.Start() + utilruntime.Must(err) + if config == nil { + log.Panic("expected cfg to not be nil") + } + + code := m.Run() + utilruntime.Must(testEnv.Stop()) + os.Exit(code) +} diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go new file mode 100644 index 0000000000..4926ff8539 --- /dev/null +++ b/internal/operator-controller/features/features.go @@ -0,0 +1,94 @@ +package features + +import ( + "github.com/go-logr/logr" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/component-base/featuregate" + + fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates" +) + +const ( + // Add new feature gates constants (strings) + // Ex: SomeFeature featuregate.Feature = "SomeFeature" + PreflightPermissions featuregate.Feature = "PreflightPermissions" + SingleOwnNamespaceInstallSupport featuregate.Feature = "SingleOwnNamespaceInstallSupport" + SyntheticPermissions featuregate.Feature = "SyntheticPermissions" + WebhookProviderCertManager featuregate.Feature = "WebhookProviderCertManager" + WebhookProviderOpenshiftServiceCA featuregate.Feature = "WebhookProviderOpenshiftServiceCA" + HelmChartSupport featuregate.Feature = "HelmChartSupport" + BoxcutterRuntime featuregate.Feature = "BoxcutterRuntime" +) + +var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + // Add new feature gate definitions + // Ex: SomeFeature: {...} + PreflightPermissions: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, + + // SingleOwnNamespaceInstallSupport enables support for installing + // registry+v1 cluster extensions with single or own namespaces modes + // i.e. with a single watch namespace. + SingleOwnNamespaceInstallSupport: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, + + // SyntheticPermissions enables support for a synthetic user permission + // model to manage operator permission boundaries + SyntheticPermissions: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, + + // WebhookProviderCertManager enables support for installing + // registry+v1 cluster extensions that include validating, + // mutating, and/or conversion webhooks with CertManager + // as the certificate provider. + WebhookProviderCertManager: { + Default: true, + PreRelease: featuregate.GA, + LockToDefault: false, + }, + + // WebhookProviderCertManager enables support for installing + // registry+v1 cluster extensions that include validating, + // mutating, and/or conversion webhooks with Openshift Service CA + // as the certificate provider. + WebhookProviderOpenshiftServiceCA: { + Default: true, + PreRelease: featuregate.GA, + LockToDefault: false, + }, + + // HelmChartSupport enables support for installing, + // updating and uninstalling Helm Charts via Cluster Extensions. + HelmChartSupport: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, + + // BoxcutterRuntime configures OLM to use the Boxcutter runtime for extension lifecycling + BoxcutterRuntime: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, +} + +var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() + +func init() { + utilruntime.Must(OperatorControllerFeatureGate.Add(operatorControllerFeatureGates)) +} + +// LogFeatureGateStates logs the state of all known feature gates. +func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) { + fgutil.LogFeatureGateStates(log, "feature gate status", fg, operatorControllerFeatureGates) +} diff --git a/internal/operator-controller/finalizers/finalizers.go b/internal/operator-controller/finalizers/finalizers.go new file mode 100644 index 0000000000..b04635a9e7 --- /dev/null +++ b/internal/operator-controller/finalizers/finalizers.go @@ -0,0 +1,14 @@ +package finalizers + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" +) + +type FinalizerFunc func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) + +func (f FinalizerFunc) Finalize(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + return f(ctx, obj) +} diff --git a/internal/operator-controller/labels/labels.go b/internal/operator-controller/labels/labels.go new file mode 100644 index 0000000000..63bf0c4931 --- /dev/null +++ b/internal/operator-controller/labels/labels.go @@ -0,0 +1,10 @@ +package labels + +const ( + OwnerKindKey = "olm.operatorframework.io/owner-kind" + OwnerNameKey = "olm.operatorframework.io/owner-name" + PackageNameKey = "olm.operatorframework.io/package-name" + BundleNameKey = "olm.operatorframework.io/bundle-name" + BundleVersionKey = "olm.operatorframework.io/bundle-version" + BundleReferenceKey = "olm.operatorframework.io/bundle-reference" +) diff --git a/internal/resolve/catalog.go b/internal/operator-controller/resolve/catalog.go similarity index 66% rename from internal/resolve/catalog.go rename to internal/operator-controller/resolve/catalog.go index 0c9dac762f..8cd1ebe81d 100644 --- a/internal/resolve/catalog.go +++ b/internal/operator-controller/resolve/catalog.go @@ -13,14 +13,15 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" "github.com/operator-framework/operator-registry/alpha/declcfg" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/bundleutil" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/compare" - "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/compare" + "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/filter" + filterutil "github.com/operator-framework/operator-controller/internal/shared/util/filter" ) type ValidationFunc func(*declcfg.Bundle) error @@ -37,18 +38,24 @@ type foundBundle struct { } // Resolve returns a Bundle from a catalog that needs to get installed on the cluster. -func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { +func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + l := log.FromContext(ctx) packageName := ext.Spec.Source.Catalog.PackageName versionRange := ext.Spec.Source.Catalog.Version channels := ext.Spec.Source.Catalog.Channels - selector, err := metav1.LabelSelectorAsSelector(&ext.Spec.Source.Catalog.Selector) - if err != nil { - return nil, nil, nil, fmt.Errorf("desired catalog selector is invalid: %w", err) - } - // A nothing (empty) seletor selects everything - if selector == labels.Nothing() { - selector = labels.Everything() + // unless overridden, default to selecting all bundles + var selector = labels.Everything() + var err error + if ext.Spec.Source.Catalog != nil { + selector, err = metav1.LabelSelectorAsSelector(ext.Spec.Source.Catalog.Selector) + if err != nil { + return nil, nil, nil, fmt.Errorf("desired catalog selector is invalid: %w", err) + } + // A nothing (empty) selector selects everything + if selector == labels.Nothing() { + selector = labels.Everything() + } } var versionRangeConstraints *mmsemver.Constraints @@ -59,18 +66,37 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx } } - resolvedBundles := []foundBundle{} + type catStat struct { + CatalogName string `json:"catalogName"` + PackageFound bool `json:"packageFound"` + TotalBundles int `json:"totalBundles"` + MatchedBundles int `json:"matchedBundles"` + } + + var catStats []*catStat + + var resolvedBundles []foundBundle var priorDeprecation *declcfg.Deprecation listOptions := []client.ListOption{ client.MatchingLabelsSelector{Selector: selector}, } - if err := r.WalkCatalogsFunc(ctx, packageName, func(ctx context.Context, cat *catalogd.ClusterCatalog, packageFBC *declcfg.DeclarativeConfig, err error) error { + if err := r.WalkCatalogsFunc(ctx, packageName, func(ctx context.Context, cat *ocv1.ClusterCatalog, packageFBC *declcfg.DeclarativeConfig, err error) error { if err != nil { return fmt.Errorf("error getting package %q from catalog %q: %w", packageName, cat.Name, err) } - var predicates []filter.Predicate[declcfg.Bundle] + cs := catStat{CatalogName: cat.Name} + catStats = append(catStats, &cs) + + if isFBCEmpty(packageFBC) { + return nil + } + + cs.PackageFound = true + cs.TotalBundles = len(packageFBC.Bundles) + + var predicates []filterutil.Predicate[declcfg.Bundle] if len(channels) > 0 { channelSet := sets.New(channels...) filteredChannels := slices.DeleteFunc(packageFBC.Channels, func(c declcfg.Channel) bool { @@ -83,7 +109,7 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx predicates = append(predicates, filter.InMastermindsSemverRange(versionRangeConstraints)) } - if ext.Spec.Source.Catalog.UpgradeConstraintPolicy != ocv1alpha1.UpgradeConstraintPolicySelfCertified && installedBundle != nil { + if ext.Spec.Source.Catalog.UpgradeConstraintPolicy != ocv1.UpgradeConstraintPolicySelfCertified && installedBundle != nil { successorPredicate, err := filter.SuccessorsOf(*installedBundle, packageFBC.Channels...) if err != nil { return fmt.Errorf("error finding upgrade edges: %w", err) @@ -92,7 +118,8 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx } // Apply the predicates to get the candidate bundles - packageFBC.Bundles = filter.Filter(packageFBC.Bundles, filter.And(predicates...)) + packageFBC.Bundles = filterutil.InPlace(packageFBC.Bundles, filterutil.And(predicates...)) + cs.MatchedBundles = len(packageFBC.Bundles) if len(packageFBC.Bundles) == 0 { return nil } @@ -152,6 +179,7 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx // Check for ambiguity if len(resolvedBundles) != 1 { + l.Info("resolution failed", "stats", catStats) return nil, nil, nil, resolutionError{ PackageName: packageName, Version: versionRange, @@ -168,12 +196,15 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx // Run validations against the resolved bundle to ensure only valid resolved bundles are being returned // Open Question: Should we grab the first valid bundle earlier? + // Answer: No, that would be a hidden resolution input, which we should avoid at all costs; the query can be + // constrained in order to eliminate the invalid bundle from the resolution. for _, validation := range r.Validations { if err := validation(resolvedBundle); err != nil { return nil, nil, nil, fmt.Errorf("validating bundle %q: %w", resolvedBundle.Name, err) } } + l.V(4).Info("resolution succeeded", "stats", catStats) return resolvedBundle, resolvedBundleVersion, priorDeprecation, nil } @@ -181,7 +212,7 @@ type resolutionError struct { PackageName string Version string Channels []string - InstalledBundle *ocv1alpha1.BundleMetadata + InstalledBundle *ocv1.BundleMetadata ResolvedBundles []foundBundle } @@ -229,22 +260,57 @@ func isDeprecated(bundle declcfg.Bundle, deprecation *declcfg.Deprecation) bool return false } -type CatalogWalkFunc func(context.Context, *catalogd.ClusterCatalog, *declcfg.DeclarativeConfig, error) error +type CatalogWalkFunc func(context.Context, *ocv1.ClusterCatalog, *declcfg.DeclarativeConfig, error) error -func CatalogWalker(listCatalogs func(context.Context, ...client.ListOption) ([]catalogd.ClusterCatalog, error), getPackage func(context.Context, *catalogd.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error)) func(ctx context.Context, packageName string, f CatalogWalkFunc, catalogListOpts ...client.ListOption) error { +func CatalogWalker( + listCatalogs func(context.Context, ...client.ListOption) ([]ocv1.ClusterCatalog, error), + getPackage func(context.Context, *ocv1.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error), +) func(ctx context.Context, packageName string, f CatalogWalkFunc, catalogListOpts ...client.ListOption) error { return func(ctx context.Context, packageName string, f CatalogWalkFunc, catalogListOpts ...client.ListOption) error { + l := log.FromContext(ctx) catalogs, err := listCatalogs(ctx, catalogListOpts...) if err != nil { return fmt.Errorf("error listing catalogs: %w", err) } + // Remove disabled catalogs from consideration + catalogs = slices.DeleteFunc(catalogs, func(c ocv1.ClusterCatalog) bool { + if c.Spec.AvailabilityMode == ocv1.AvailabilityModeUnavailable { + l.Info("excluding ClusterCatalog from resolution process since it is disabled", "catalog", c.Name) + return true + } + return false + }) + + availableCatalogNames := mapSlice(catalogs, func(c ocv1.ClusterCatalog) string { return c.Name }) + l.Info("using ClusterCatalogs for resolution", "catalogs", availableCatalogNames) + for i := range catalogs { cat := &catalogs[i] + + // process enabled catalogs fbc, fbcErr := getPackage(ctx, cat, packageName) + if walkErr := f(ctx, cat, fbc, fbcErr); walkErr != nil { return walkErr } } + return nil } } + +func isFBCEmpty(fbc *declcfg.DeclarativeConfig) bool { + if fbc == nil { + return true + } + return len(fbc.Packages) == 0 && len(fbc.Channels) == 0 && len(fbc.Bundles) == 0 && len(fbc.Deprecations) == 0 && len(fbc.Others) == 0 +} + +func mapSlice[I any, O any](in []I, f func(I) O) []O { + out := make([]O, len(in)) + for i := range in { + out[i] = f(in[i]) + } + return out +} diff --git a/internal/resolve/catalog_test.go b/internal/operator-controller/resolve/catalog_test.go similarity index 63% rename from internal/resolve/catalog_test.go rename to internal/operator-controller/resolve/catalog_test.go index 17d0007bc0..21232bc4df 100644 --- a/internal/resolve/catalog_test.go +++ b/internal/operator-controller/resolve/catalog_test.go @@ -12,22 +12,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/rand" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/features" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) func TestInvalidClusterExtensionVersionRange(t *testing.T) { r := CatalogResolver{} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, []string{}, "foobar", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "foobar", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, `desired version range "foobar" is invalid: improper constraint: foobar`) } @@ -37,39 +34,39 @@ func TestErrorWalkingCatalogs(t *testing.T) { return fmt.Errorf("fake error") }} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, "error walking catalogs: fake error") } func TestErrorGettingPackage(t *testing.T) { w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return nil, nil, fmt.Errorf("fake error") }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`error walking catalogs: error getting package %q from catalog "a": fake error`, pkgName)) } func TestPackageDoesNotExist(t *testing.T) { w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q`, pkgName)) } @@ -77,18 +74,18 @@ func TestPackageDoesNotExist(t *testing.T) { func TestPackageExists(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "3.0.0"), *gotBundle) @@ -99,13 +96,13 @@ func TestPackageExists(t *testing.T) { func TestValidationFailed(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } @@ -117,7 +114,7 @@ func TestValidationFailed(t *testing.T) { }, }, } - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) } @@ -125,18 +122,18 @@ func TestValidationFailed(t *testing.T) { func TestVersionDoesNotExist(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "4.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "4.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "4.0.0"`, pkgName)) } @@ -144,18 +141,18 @@ func TestVersionDoesNotExist(t *testing.T) { func TestVersionExists(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <2.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <2.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.2"), *gotBundle) @@ -166,18 +163,18 @@ func TestVersionExists(t *testing.T) { func TestChannelDoesNotExist(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"stable"}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"stable"}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q in channels [stable]`, pkgName)) } @@ -185,18 +182,18 @@ func TestChannelDoesNotExist(t *testing.T) { func TestChannelExists(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"beta"}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"beta"}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.2"), *gotBundle) @@ -207,18 +204,18 @@ func TestChannelExists(t *testing.T) { func TestChannelExistsButNotVersion(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"beta"}, "3.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"beta"}, "3.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "3.0.0" in channels [beta]`, pkgName)) } @@ -226,18 +223,18 @@ func TestChannelExistsButNotVersion(t *testing.T) { func TestVersionExistsButNotChannel(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"stable"}, "1.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"stable"}, "1.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "1.0.0" in channels [stable]`, pkgName)) } @@ -245,18 +242,18 @@ func TestVersionExistsButNotChannel(t *testing.T) { func TestChannelAndVersionExist(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"alpha"}, "0.1.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"alpha"}, "0.1.0", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "0.1.0"), *gotBundle) @@ -267,18 +264,18 @@ func TestChannelAndVersionExist(t *testing.T) { func TestPreferNonDeprecated(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, ">=0.1.0 <=1.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=0.1.0 <=1.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "0.1.0"), *gotBundle) @@ -289,18 +286,18 @@ func TestPreferNonDeprecated(t *testing.T) { func TestAcceptDeprecated(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.1"), *gotBundle) @@ -311,10 +308,10 @@ func TestAcceptDeprecated(t *testing.T) { func TestPackageVariationsBetweenCatalogs(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { fbc := &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Bundles: []declcfg.Bundle{genBundle(pkgName, "1.0.0")}, @@ -330,7 +327,7 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { } return fbc, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { fbc := &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Bundles: []declcfg.Bundle{genBundle(pkgName, "1.0.1")}, @@ -346,14 +343,14 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { } return fbc, nil, nil }, - "d": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "d": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { fbc := &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Bundles: []declcfg.Bundle{genBundle(pkgName, "1.0.2")}, } return fbc, nil, nil }, - "e": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "e": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { fbc := &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Bundles: []declcfg.Bundle{genBundle(pkgName, "1.0.3")}, @@ -369,7 +366,7 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { } return fbc, nil, nil }, - "f": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "f": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { fbc := &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Bundles: []declcfg.Bundle{ @@ -383,7 +380,7 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} t.Run("when bundle candidates for a package are deprecated in all but one catalog", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.3", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.3", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) // We choose the only non-deprecated package @@ -393,29 +390,29 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { }) t.Run("when bundle candidates are found and deprecated in multiple catalogs", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) // We will not make a decision on which catalog to use - assert.ErrorContains(t, err, "in multiple catalogs with the same priority [b c]") + require.ErrorContains(t, err, "in multiple catalogs with the same priority [b c]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) }) t.Run("when bundle candidates are found and not deprecated in multiple catalogs", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.4", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.4", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) // We will not make a decision on which catalog to use - assert.ErrorContains(t, err, "in multiple catalogs with the same priority [d f]") + require.ErrorContains(t, err, "in multiple catalogs with the same priority [d f]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) }) t.Run("highest semver bundle is chosen when candidates are all from the same catalog", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.4 <=1.0.5", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.4 <=1.0.5", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) // Bundles within one catalog for a package will be sorted by semver and deprecation and the best is returned @@ -426,22 +423,21 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { } func TestUpgradeFoundLegacy(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false) pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - installedBundle := &ocv1alpha1.BundleMetadata{ + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) + installedBundle := &ocv1.BundleMetadata{ Name: bundleName(pkgName, "0.1.0"), Version: "0.1.0", } @@ -454,22 +450,21 @@ func TestUpgradeFoundLegacy(t *testing.T) { } func TestUpgradeNotFoundLegacy(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false) pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.0 >=2.0.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - installedBundle := &ocv1alpha1.BundleMetadata{ + ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.0 >=2.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) + installedBundle := &ocv1.BundleMetadata{ Name: bundleName(pkgName, "0.1.0"), Version: "0.1.0", } @@ -478,78 +473,22 @@ func TestUpgradeNotFoundLegacy(t *testing.T) { assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no bundles found for package %q matching version "<1.0.0 >=2.0.0"`, pkgName)) } -func TestUpgradeFoundSemver(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true) - pkgName := randPkg() - w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return &declcfg.DeclarativeConfig{}, nil, nil - }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return &declcfg.DeclarativeConfig{}, nil, nil - }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return genPackage(pkgName), nil, nil - }, - } - r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - installedBundle := &ocv1alpha1.BundleMetadata{ - Name: bundleName(pkgName, "1.0.0"), - Version: "1.0.0", - } - // there is a legacy upgrade edge from 1.0.0 to 2.0.0, but we are using semver semantics here. - // therefore: - // 1.0.0 => 1.0.2 is what we expect - gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, installedBundle) - require.NoError(t, err) - assert.Equal(t, genBundle(pkgName, "1.0.2"), *gotBundle) - assert.Equal(t, bsemver.MustParse("1.0.2"), *gotVersion) - assert.Equal(t, ptr.To(packageDeprecation(pkgName)), gotDeprecation) -} - -func TestUpgradeNotFoundSemver(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true) - pkgName := randPkg() - w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return &declcfg.DeclarativeConfig{}, nil, nil - }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return &declcfg.DeclarativeConfig{}, nil, nil - }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return genPackage(pkgName), nil, nil - }, - } - r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "!=0.1.0", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - installedBundle := &ocv1alpha1.BundleMetadata{ - Name: bundleName(pkgName, "0.1.0"), - Version: "0.1.0", - } - // there are legacy upgrade edges from 0.1.0 to 1.0.x, but we are using semver semantics here. - // therefore, we expect to fail because there are no semver-compatible upgrade edges from 0.1.0. - _, _, _, err := r.Resolve(context.Background(), ce, installedBundle) - assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no bundles found for package %q matching version "!=0.1.0"`, pkgName)) -} - func TestDowngradeFound(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.2", ocv1alpha1.UpgradeConstraintPolicySelfCertified) - installedBundle := &ocv1alpha1.BundleMetadata{ + ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.2", ocv1.UpgradeConstraintPolicySelfCertified) + installedBundle := &ocv1.BundleMetadata{ Name: bundleName(pkgName, "1.0.2"), Version: "1.0.2", } @@ -565,19 +504,19 @@ func TestDowngradeFound(t *testing.T) { func TestDowngradeNotFound(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, ">0.1.0 <1.0.0", ocv1alpha1.UpgradeConstraintPolicySelfCertified) - installedBundle := &ocv1alpha1.BundleMetadata{ + ce := buildFooClusterExtension(pkgName, []string{}, ">0.1.0 <1.0.0", ocv1.UpgradeConstraintPolicySelfCertified) + installedBundle := &ocv1.BundleMetadata{ Name: bundleName(pkgName, "1.0.2"), Version: "1.0.2", } @@ -589,14 +528,14 @@ func TestDowngradeNotFound(t *testing.T) { func TestCatalogWalker(t *testing.T) { t.Run("error listing catalogs", func(t *testing.T) { w := CatalogWalker( - func(ctx context.Context, option ...client.ListOption) ([]catalogd.ClusterCatalog, error) { + func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) { return nil, fmt.Errorf("fake error") }, - func(context.Context, *catalogd.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { + func(context.Context, *ocv1.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { return nil, nil }, ) - walkFunc := func(ctx context.Context, cat *catalogd.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { + walkFunc := func(ctx context.Context, cat *ocv1.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { return nil } assert.EqualError(t, w(context.Background(), "", walkFunc), "error listing catalogs: fake error") @@ -604,14 +543,14 @@ func TestCatalogWalker(t *testing.T) { t.Run("error getting package", func(t *testing.T) { w := CatalogWalker( - func(ctx context.Context, option ...client.ListOption) ([]catalogd.ClusterCatalog, error) { - return []catalogd.ClusterCatalog{{ObjectMeta: metav1.ObjectMeta{Name: "a"}}}, nil + func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) { + return []ocv1.ClusterCatalog{{ObjectMeta: metav1.ObjectMeta{Name: "a"}}}, nil }, - func(context.Context, *catalogd.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { + func(context.Context, *ocv1.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { return nil, fmt.Errorf("fake error getting package FBC") }, ) - walkFunc := func(ctx context.Context, cat *catalogd.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { + walkFunc := func(ctx context.Context, cat *ocv1.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { return err } assert.EqualError(t, w(context.Background(), "", walkFunc), "fake error getting package FBC") @@ -619,40 +558,38 @@ func TestCatalogWalker(t *testing.T) { t.Run("success", func(t *testing.T) { w := CatalogWalker( - func(ctx context.Context, option ...client.ListOption) ([]catalogd.ClusterCatalog, error) { - return []catalogd.ClusterCatalog{ + func(ctx context.Context, option ...client.ListOption) ([]ocv1.ClusterCatalog, error) { + return []ocv1.ClusterCatalog{ {ObjectMeta: metav1.ObjectMeta{Name: "a"}}, {ObjectMeta: metav1.ObjectMeta{Name: "b"}}, }, nil }, - func(context.Context, *catalogd.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { + func(context.Context, *ocv1.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) { return &declcfg.DeclarativeConfig{}, nil }, ) seenCatalogs := []string{} - walkFunc := func(ctx context.Context, cat *catalogd.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { + walkFunc := func(ctx context.Context, cat *ocv1.ClusterCatalog, fbc *declcfg.DeclarativeConfig, err error) error { seenCatalogs = append(seenCatalogs, cat.Name) return nil } - assert.NoError(t, w(context.Background(), "", walkFunc)) + require.NoError(t, w(context.Background(), "", walkFunc)) assert.Equal(t, []string{"a", "b"}, seenCatalogs) }) } -func buildFooClusterExtension(pkg string, channels []string, version string, upgradeConstraintPolicy ocv1alpha1.UpgradeConstraintPolicy) *ocv1alpha1.ClusterExtension { - return &ocv1alpha1.ClusterExtension{ +func buildFooClusterExtension(pkg string, channels []string, version string, upgradeConstraintPolicy ocv1.UpgradeConstraintPolicy) *ocv1.ClusterExtension { + return &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: pkg, }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{Name: "default"}, - }, - Source: ocv1alpha1.SourceConfig{ + Spec: ocv1.ClusterExtensionSpec{ + Namespace: "default", + ServiceAccount: ocv1.ServiceAccountReference{Name: "default"}, + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: pkg, Version: version, Channels: channels, @@ -663,13 +600,13 @@ func buildFooClusterExtension(pkg string, channels []string, version string, upg } } -type getPackageFunc func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) +type getPackageFunc func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) type staticCatalogWalker map[string]getPackageFunc func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f CatalogWalkFunc, opts ...client.ListOption) error { for k, v := range w { - cat := &catalogd.ClusterCatalog{ + cat := &ocv1.ClusterCatalog{ ObjectMeta: metav1.ObjectMeta{ Name: k, Labels: map[string]string{ @@ -681,7 +618,7 @@ func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f Catal for _, opt := range opts { opt.ApplyToList(&options) } - if !options.LabelSelector.Matches(labels.Set(cat.ObjectMeta.Labels)) { + if !options.LabelSelector.Matches(labels.Set(cat.Labels)) { continue } fbc, catCfg, fbcErr := v() @@ -762,15 +699,15 @@ func genPackage(pkg string) *declcfg.DeclarativeConfig { func TestInvalidClusterExtensionCatalogMatchExpressions(t *testing.T) { r := CatalogResolver{} - ce := &ocv1alpha1.ClusterExtension{ + ce := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - Catalog: &ocv1alpha1.CatalogSource{ + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + Catalog: &ocv1.CatalogFilter{ PackageName: "foo", - Selector: metav1.LabelSelector{ + Selector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { Key: "name", @@ -789,20 +726,20 @@ func TestInvalidClusterExtensionCatalogMatchExpressions(t *testing.T) { func TestInvalidClusterExtensionCatalogMatchLabelsName(t *testing.T) { w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage("foo"), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := &ocv1alpha1.ClusterExtension{ + ce := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - Catalog: &ocv1alpha1.CatalogSource{ + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + Catalog: &ocv1.CatalogFilter{ PackageName: "foo", - Selector: metav1.LabelSelector{ + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"": "value"}, }, }, @@ -815,20 +752,20 @@ func TestInvalidClusterExtensionCatalogMatchLabelsName(t *testing.T) { func TestInvalidClusterExtensionCatalogMatchLabelsValue(t *testing.T) { w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage("foo"), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := &ocv1alpha1.ClusterExtension{ + ce := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ - Catalog: &ocv1alpha1.CatalogSource{ + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + Catalog: &ocv1.CatalogFilter{ PackageName: "foo", - Selector: metav1.LabelSelector{ + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"name": "&value"}, }, }, @@ -840,38 +777,40 @@ func TestInvalidClusterExtensionCatalogMatchLabelsValue(t *testing.T) { } func TestClusterExtensionMatchLabel(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false) pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - ce.Spec.Source.Catalog.Selector.MatchLabels = map[string]string{"olm.operatorframework.io/metadata.name": "b"} + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) + ce.Spec.Source.Catalog.Selector = &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": "b"}, + } _, _, _, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) } func TestClusterExtensionNoMatchLabel(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false) pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) - ce.Spec.Source.Catalog.Selector.MatchLabels = map[string]string{"olm.operatorframework.io/metadata.name": "a"} + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) + ce.Spec.Source.Catalog.Selector = &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": "a"}, + } _, _, _, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) @@ -881,7 +820,7 @@ func TestClusterExtensionNoMatchLabel(t *testing.T) { func TestUnequalPriority(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Channels: []declcfg.Channel{ @@ -893,9 +832,9 @@ func TestUnequalPriority(t *testing.T) { genBundle(pkgName, "1.0.0"), }, Deprecations: []declcfg.Deprecation{}, - }, &catalogd.ClusterCatalogSpec{Priority: 1}, nil + }, &ocv1.ClusterCatalogSpec{Priority: 1}, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{ Packages: []declcfg.Package{{Name: pkgName}}, Channels: []declcfg.Channel{ @@ -907,12 +846,12 @@ func TestUnequalPriority(t *testing.T) { genBundle(pkgName, "1.1.0"), }, Deprecations: []declcfg.Deprecation{}, - }, &catalogd.ClusterCatalogSpec{Priority: 0}, nil + }, &ocv1.ClusterCatalogSpec{Priority: 0}, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) _, gotVersion, _, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) require.Equal(t, bsemver.MustParse("1.0.0"), *gotVersion) @@ -921,22 +860,22 @@ func TestUnequalPriority(t *testing.T) { func TestMultiplePriority(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return genPackage(pkgName), &catalogd.ClusterCatalogSpec{Priority: 1}, nil + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { + return genPackage(pkgName), &ocv1.ClusterCatalogSpec{Priority: 1}, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return genPackage(pkgName), &catalogd.ClusterCatalogSpec{Priority: 0}, nil + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { + return genPackage(pkgName), &ocv1.ClusterCatalogSpec{Priority: 0}, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { - return genPackage(pkgName), &catalogd.ClusterCatalogSpec{Priority: 1}, nil + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { + return genPackage(pkgName), &ocv1.ClusterCatalogSpec{Priority: 1}, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) - assert.ErrorContains(t, err, "in multiple catalogs with the same priority [a b c]") + require.ErrorContains(t, err, "in multiple catalogs with the same priority [a b c]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) @@ -945,21 +884,93 @@ func TestMultiplePriority(t *testing.T) { func TestMultipleChannels(t *testing.T) { pkgName := randPkg() w := staticCatalogWalker{ - "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "a": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "b": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return &declcfg.DeclarativeConfig{}, nil, nil }, - "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + "c": func() (*declcfg.DeclarativeConfig, *ocv1.ClusterCatalogSpec, error) { return genPackage(pkgName), nil, nil }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, []string{"beta", "alpha"}, "", ocv1alpha1.UpgradeConstraintPolicyCatalogProvided) + ce := buildFooClusterExtension(pkgName, []string{"beta", "alpha"}, "", ocv1.UpgradeConstraintPolicyCatalogProvided) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "2.0.0"), *gotBundle) assert.Equal(t, bsemver.MustParse("2.0.0"), *gotVersion) assert.Equal(t, ptr.To(packageDeprecation(pkgName)), gotDeprecation) } + +func TestAllCatalogsDisabled(t *testing.T) { + pkgName := randPkg() + listCatalogs := func(ctx context.Context, options ...client.ListOption) ([]ocv1.ClusterCatalog, error) { + return []ocv1.ClusterCatalog{ + { + Spec: ocv1.ClusterCatalogSpec{ + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + }, + { + Spec: ocv1.ClusterCatalogSpec{ + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + }, + }, nil + } + + getPackage := func(ctx context.Context, cat *ocv1.ClusterCatalog, packageName string) (*declcfg.DeclarativeConfig, error) { + panic("getPackage should never be called when all catalogs are disabled") + } + + r := CatalogResolver{ + WalkCatalogsFunc: CatalogWalker(listCatalogs, getPackage), + } + + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) + _, _, _, err := r.Resolve(context.Background(), ce, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "no bundles found for package") +} + +func TestSomeCatalogsDisabled(t *testing.T) { + pkgName := randPkg() + listCatalogs := func(ctx context.Context, options ...client.ListOption) ([]ocv1.ClusterCatalog, error) { + return []ocv1.ClusterCatalog{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "enabledCatalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Priority: 1, // Higher priority + AvailabilityMode: ocv1.AvailabilityModeAvailable, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "disabledCatalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Priority: 0, // Lower priority (but disabled) + AvailabilityMode: ocv1.AvailabilityModeUnavailable, + }, + }, + }, nil + } + + getPackage := func(ctx context.Context, cat *ocv1.ClusterCatalog, packageName string) (*declcfg.DeclarativeConfig, error) { + // Only enabled catalog should be processed + return genPackage(pkgName), nil + } + + r := CatalogResolver{ + WalkCatalogsFunc: CatalogWalker(listCatalogs, getPackage), + } + + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0", ocv1.UpgradeConstraintPolicyCatalogProvided) + gotBundle, gotVersion, _, err := r.Resolve(context.Background(), ce, nil) + require.NoError(t, err) + require.NotNil(t, gotBundle) + require.Equal(t, bsemver.MustParse("3.0.0"), *gotVersion) +} diff --git a/internal/operator-controller/resolve/resolver.go b/internal/operator-controller/resolve/resolver.go new file mode 100644 index 0000000000..625111d631 --- /dev/null +++ b/internal/operator-controller/resolve/resolver.go @@ -0,0 +1,21 @@ +package resolve + +import ( + "context" + + bsemver "github.com/blang/semver/v4" + + "github.com/operator-framework/operator-registry/alpha/declcfg" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +type Resolver interface { + Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) +} + +type Func func(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) + +func (f Func) Resolve(ctx context.Context, ext *ocv1.ClusterExtension, installedBundle *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { + return f(ctx, ext, installedBundle) +} diff --git a/internal/resolve/validation.go b/internal/operator-controller/resolve/validation.go similarity index 100% rename from internal/resolve/validation.go rename to internal/operator-controller/resolve/validation.go diff --git a/internal/resolve/validation_test.go b/internal/operator-controller/resolve/validation_test.go similarity index 100% rename from internal/resolve/validation_test.go rename to internal/operator-controller/resolve/validation_test.go diff --git a/internal/operator-controller/rukpak/bundle/config.go b/internal/operator-controller/rukpak/bundle/config.go new file mode 100644 index 0000000000..7ec4bfdf03 --- /dev/null +++ b/internal/operator-controller/rukpak/bundle/config.go @@ -0,0 +1,124 @@ +package bundle + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" +) + +type Config struct { + WatchNamespace *string `json:"watchNamespace"` +} + +// UnmarshalConfig returns a deserialized *bundle.Config based on bytes and validated +// against rv1 and the desired install namespaces. It will error if: +// - rv is nil +// - bytes is not a valid YAML/JSON object +// - bytes is a valid YAML/JSON object but does not follow the registry+v1 schema +// - if bytes is nil, a nil *bundle.Config is returned with no error +func UnmarshalConfig(bytes []byte, rv1 RegistryV1, installNamespace string) (*Config, error) { + if bytes == nil { + return nil, nil + } + + bundleConfig := &Config{} + if err := yaml.UnmarshalStrict(bytes, bundleConfig); err != nil { + return nil, fmt.Errorf("error unmarshalling registry+v1 configuration: %w", formatUnmarshalError(err)) + } + + // collect bundle install modes + bundleInstallModeSet := sets.New(rv1.CSV.Spec.InstallModes...) + + if err := validateConfig(bundleConfig, installNamespace, bundleInstallModeSet); err != nil { + return nil, fmt.Errorf("error unmarshalling registry+v1 configuration: %w", err) + } + + return bundleConfig, nil +} + +// validateConfig validates a *bundle.Config against the bundle's supported install modes and the user-give installNamespace. +func validateConfig(config *Config, installNamespace string, bundleInstallModeSet sets.Set[v1alpha1.InstallMode]) error { + // no config, no problem + if config == nil { + return nil + } + + // if the bundle does not support the watchNamespace configuration and it is set, treat it like any unknown field + if config.WatchNamespace != nil && !isWatchNamespaceConfigSupported(bundleInstallModeSet) { + return errors.New(`unknown field "watchNamespace"`) + } + + // if watchNamespace is required then ensure that it is set + if config.WatchNamespace == nil && isWatchNamespaceConfigRequired(bundleInstallModeSet) { + return errors.New(`required field "watchNamespace" is missing`) + } + + // if watchNamespace is set then ensure it is a valid namespace + if config.WatchNamespace != nil { + if errs := validation.IsDNS1123Subdomain(*config.WatchNamespace); len(errs) > 0 { + return fmt.Errorf("invalid 'watchNamespace' %q: namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", *config.WatchNamespace) + } + } + + // only accept install namespace if OwnNamespace install mode is supported + if config.WatchNamespace != nil && *config.WatchNamespace == installNamespace && + !bundleInstallModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}) { + return fmt.Errorf("invalid 'watchNamespace' %q: must not be install namespace (%s)", *config.WatchNamespace, installNamespace) + } + + // only accept non-install namespace is SingleNamespace is supported + if config.WatchNamespace != nil && *config.WatchNamespace != installNamespace && + !bundleInstallModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}) { + return fmt.Errorf("invalid 'watchNamespace' %q: must be install namespace (%s)", *config.WatchNamespace, installNamespace) + } + + return nil +} + +// isWatchNamespaceConfigSupported returns true when the bundle exposes a watchNamespace configuration. This happens when: +// - SingleNamespace and/or OwnNamespace install modes are supported +func isWatchNamespaceConfigSupported(bundleInstallModeSet sets.Set[v1alpha1.InstallMode]) bool { + return bundleInstallModeSet.HasAny( + v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}, + v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}, + ) +} + +// isWatchNamespaceConfigRequired returns true if the watchNamespace configuration is required. This happens when +// AllNamespaces install mode is not supported and SingleNamespace and/or OwnNamespace is supported +func isWatchNamespaceConfigRequired(bundleInstallModeSet sets.Set[v1alpha1.InstallMode]) bool { + return isWatchNamespaceConfigSupported(bundleInstallModeSet) && + !bundleInstallModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}) +} + +// formatUnmarshalError format JSON unmarshal errors to be more readable +func formatUnmarshalError(err error) error { + var unmarshalErr *json.UnmarshalTypeError + if errors.As(err, &unmarshalErr) { + if unmarshalErr.Field == "" { + return errors.New("input is not a valid JSON object") + } else { + return fmt.Errorf("invalid value type for field %q: expected %q but got %q", unmarshalErr.Field, unmarshalErr.Type.String(), unmarshalErr.Value) + } + } + + // unwrap error until the core and process it + for { + unwrapped := errors.Unwrap(err) + if unwrapped == nil { + // usually the errors present in the form json: or yaml: + // we want to extract if we can + errMessageComponents := strings.Split(err.Error(), ":") + coreErrMessage := strings.TrimSpace(errMessageComponents[len(errMessageComponents)-1]) + return errors.New(coreErrMessage) + } + err = unwrapped + } +} diff --git a/internal/operator-controller/rukpak/bundle/config_test.go b/internal/operator-controller/rukpak/bundle/config_test.go new file mode 100644 index 0000000000..a6c4b394a2 --- /dev/null +++ b/internal/operator-controller/rukpak/bundle/config_test.go @@ -0,0 +1,310 @@ +package bundle_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/utils/ptr" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_UnmarshalConfig(t *testing.T) { + for _, tc := range []struct { + name string + rawConfig []byte + supportedInstallModes []v1alpha1.InstallModeType + installNamespace string + expectedErrMessage string + expectedConfig *bundle.Config + }{ + { + name: "returns nil for nil config", + rawConfig: nil, + expectedConfig: nil, + }, + { + name: "accepts json config", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "accepts yaml config", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`watchNamespace: some-namespace`), + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "rejects invalid json", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"hello`), + expectedErrMessage: `error unmarshalling registry+v1 configuration: found unexpected end of stream`, + }, + { + name: "rejects valid json that isn't of object type", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`true`), + expectedErrMessage: `error unmarshalling registry+v1 configuration: input is not a valid JSON object`, + }, + { + name: "rejects additional fields", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`somekey: somevalue`), + expectedErrMessage: `error unmarshalling registry+v1 configuration: unknown field "somekey"`, + }, + { + name: "rejects valid json but invalid registry+v1", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": {"hello": "there"}}`), + expectedErrMessage: `error unmarshalling registry+v1 configuration: invalid value type for field "watchNamespace": expected "string" but got "object"`, + }, + { + name: "rejects bad namespace format", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "bad-Namespace-"}`), + expectedErrMessage: "invalid 'watchNamespace' \"bad-Namespace-\": namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", + }, + { + name: "rejects with unknown field when install modes {AllNamespaces}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedErrMessage: "unknown field \"watchNamespace\"", + }, + { + name: "rejects with unknown field when install modes {MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedErrMessage: "unknown field \"watchNamespace\"", + }, + { + name: "reject with unknown field when install modes {AllNamespaces, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedErrMessage: "unknown field \"watchNamespace\"", + }, + { + name: "reject with required field when install modes {OwnNamespace} and watchNamespace is null", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "reject with required field when install modes {OwnNamespace} and watchNamespace is missing", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{}`), + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "reject with required field when install modes {MultiNamespace, OwnNamespace} and watchNamespace is null", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "reject with required field when install modes {MultiNamespace, OwnNamespace} and watchNamespace is missing", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{}`), + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "accepts when install modes {SingleNamespace} and watchNamespace != install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "accepts when install modes {AllNamespaces, SingleNamespace} and watchNamespace != install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "accepts when install modes {MultiNamespace, SingleNamespace} and watchNamespace != install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "accepts when install modes {OwnNamespace, SingleNamespace} and watchNamespace != install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "not-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "rejects when install modes {SingleNamespace} and watchNamespace == install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "some-namespace", + expectedErrMessage: "invalid 'watchNamespace' \"some-namespace\": must not be install namespace (some-namespace)", + }, + { + name: "rejects when install modes {AllNamespaces, SingleNamespace} and watchNamespace == install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "some-namespace", + expectedErrMessage: "invalid 'watchNamespace' \"some-namespace\": must not be install namespace (some-namespace)", + }, + { + name: "rejects when install modes {MultiNamespace, SingleNamespace} and watchNamespace == install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "some-namespace", + expectedErrMessage: "invalid 'watchNamespace' \"some-namespace\": must not be install namespace (some-namespace)", + }, + { + name: "accepts when install modes {AllNamespaces, OwnNamespace} and watchNamespace == install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "accepts when install modes {OwnNamespace, SingleNamespace} and watchNamespace == install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: ptr.To("some-namespace"), + }, + }, + { + name: "rejects when install modes {AllNamespaces, OwnNamespace} and watchNamespace != install namespace", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": "some-namespace"}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "invalid 'watchNamespace' \"some-namespace\": must be install namespace (not-some-namespace)", + }, + { + name: "rejects with required field error when install modes {SingleNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace, OwnNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace, MultiNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace} and watchNamespace is missing", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + rawConfig: []byte(`{}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace, OwnNamespace} and watchNamespace is missing", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace, MultiNamespace} and watchNamespace is missing", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "rejects with required field error when install modes {SingleNamespace, OwnNamespace, MultiNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "required field \"watchNamespace\" is missing", + }, + { + name: "accepts null watchNamespace when install modes {AllNamespaces, OwnNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: nil, + }, + }, + { + name: "accepts null watchNamespace when install modes {AllNamespaces, OwnNamespace, MultiNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{"watchNamespace": null}`), + installNamespace: "not-some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: nil, + }, + }, + { + name: "accepts no watchNamespace when install modes {AllNamespaces, OwnNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{}`), + installNamespace: "not-some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: nil, + }, + }, + { + name: "accepts no watchNamespace when install modes {AllNamespaces, OwnNamespace, MultiNamespace} and watchNamespace is nil", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + rawConfig: []byte(`{}`), + installNamespace: "not-some-namespace", + expectedConfig: &bundle.Config{ + WatchNamespace: nil, + }, + }, + { + name: "rejects with format error when install modes are {SingleNamespace, OwnNamespace} and watchNamespace is ''", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + rawConfig: []byte(`{"watchNamespace": ""}`), + installNamespace: "not-some-namespace", + expectedErrMessage: "invalid 'watchNamespace' \"\": namespace must consist of lower case alphanumeric characters", + }, + } { + t.Run(tc.name, func(t *testing.T) { + var rv1 bundle.RegistryV1 + if tc.supportedInstallModes != nil { + rv1 = bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("test-operator"). + WithInstallModeSupportFor(tc.supportedInstallModes...). + Build(), + } + } + + config, err := bundle.UnmarshalConfig(tc.rawConfig, rv1, tc.installNamespace) + require.Equal(t, tc.expectedConfig, config) + if tc.expectedErrMessage != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErrMessage) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/internal/operator-controller/rukpak/bundle/registryv1.go b/internal/operator-controller/rukpak/bundle/registryv1.go new file mode 100644 index 0000000000..cffc374e91 --- /dev/null +++ b/internal/operator-controller/rukpak/bundle/registryv1.go @@ -0,0 +1,19 @@ +package bundle + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" +) + +const ( + BundleConfigWatchNamespaceKey = "watchNamespace" +) + +type RegistryV1 struct { + PackageName string + CSV v1alpha1.ClusterServiceVersion + CRDs []apiextensionsv1.CustomResourceDefinition + Others []unstructured.Unstructured +} diff --git a/internal/operator-controller/rukpak/bundle/source/source.go b/internal/operator-controller/rukpak/bundle/source/source.go new file mode 100644 index 0000000000..0f5d3f185c --- /dev/null +++ b/internal/operator-controller/rukpak/bundle/source/source.go @@ -0,0 +1,178 @@ +package source + +import ( + "encoding/json" + "errors" + "fmt" + "io/fs" + "path/filepath" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-registry/alpha/property" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + registry "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/operator-registry" +) + +type BundleSource interface { + GetBundle() (bundle.RegistryV1, error) +} + +type RegistryV1Properties struct { + Properties []property.Property `json:"properties"` +} + +// identitySource is a bundle source that returns itself +type identitySource bundle.RegistryV1 + +func (r identitySource) GetBundle() (bundle.RegistryV1, error) { + return bundle.RegistryV1(r), nil +} + +func FromBundle(rv1 bundle.RegistryV1) BundleSource { + return identitySource(rv1) +} + +// FromFS returns a BundleSource that loads a registry+v1 bundle from a filesystem. +// The filesystem is expected to conform to the registry+v1 format: +// metadata/annotations.yaml +// metadata/properties.yaml +// manifests/ +// - csv.yaml +// - ... +// +// manifests directory should not contain subdirectories +func FromFS(fs fs.FS) BundleSource { + return fsBundleSource{ + FS: fs, + } +} + +type fsBundleSource struct { + FS fs.FS +} + +func (f fsBundleSource) GetBundle() (bundle.RegistryV1, error) { + reg := bundle.RegistryV1{} + annotationsFileData, err := fs.ReadFile(f.FS, filepath.Join("metadata", "annotations.yaml")) + if err != nil { + return reg, err + } + annotationsFile := registry.AnnotationsFile{} + if err := yaml.Unmarshal(annotationsFileData, &annotationsFile); err != nil { + return reg, err + } + reg.PackageName = annotationsFile.Annotations.PackageName + + const manifestsDir = "manifests" + foundCSV := false + if err := fs.WalkDir(f.FS, manifestsDir, func(path string, e fs.DirEntry, err error) error { + if err != nil { + return err + } + if e.IsDir() { + if path == manifestsDir { + return nil + } + return fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, path) + } + manifestFile, err := f.FS.Open(path) + if err != nil { + return err + } + defer manifestFile.Close() + + result := resource.NewLocalBuilder().Unstructured().Flatten().Stream(manifestFile, path).Do() + if err := result.Err(); err != nil { + return err + } + if err := result.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + switch info.Object.GetObjectKind().GroupVersionKind().Kind { + case "ClusterServiceVersion": + csv := v1alpha1.ClusterServiceVersion{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(info.Object.(*unstructured.Unstructured).Object, &csv); err != nil { + return err + } + reg.CSV = csv + foundCSV = true + case "CustomResourceDefinition": + crd := apiextensionsv1.CustomResourceDefinition{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(info.Object.(*unstructured.Unstructured).Object, &crd); err != nil { + return err + } + reg.CRDs = append(reg.CRDs, crd) + default: + reg.Others = append(reg.Others, *info.Object.(*unstructured.Unstructured)) + } + return nil + }); err != nil { + return fmt.Errorf("error parsing objects in %q: %v", path, err) + } + return nil + }); err != nil { + return reg, err + } + + if !foundCSV { + return reg, fmt.Errorf("no ClusterServiceVersion found in %q", manifestsDir) + } + + if err := copyMetadataPropertiesToCSV(®.CSV, f.FS); err != nil { + return reg, err + } + + return reg, nil +} + +// copyMetadataPropertiesToCSV copies properties from `metadata/propeties.yaml` (in the filesystem fsys) into +// the CSV's `.metadata.annotations['olm.properties']` value, preserving any properties that are already +// present in the annotations. +func copyMetadataPropertiesToCSV(csv *v1alpha1.ClusterServiceVersion, fsys fs.FS) error { + var allProperties []property.Property + + // First load existing properties from the CSV. We want to preserve these. + if csvPropertiesJSON, ok := csv.Annotations["olm.properties"]; ok { + var csvProperties []property.Property + if err := json.Unmarshal([]byte(csvPropertiesJSON), &csvProperties); err != nil { + return fmt.Errorf("failed to unmarshal csv.metadata.annotations['olm.properties']: %w", err) + } + allProperties = append(allProperties, csvProperties...) + } + + // Next, load properties from the metadata/properties.yaml file, if it exists. + metadataPropertiesJSON, err := fs.ReadFile(fsys, filepath.Join("metadata", "properties.yaml")) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to read properties.yaml file: %w", err) + } + + // If there are no properties, we can stick with whatever + // was already present in the CSV annotations. + if len(metadataPropertiesJSON) == 0 { + return nil + } + + // Otherwise, we need to parse the properties.yaml file and + // append its properties into the CSV annotation. + var metadataProperties RegistryV1Properties + if err := yaml.Unmarshal(metadataPropertiesJSON, &metadataProperties); err != nil { + return fmt.Errorf("failed to unmarshal metadata/properties.yaml: %w", err) + } + allProperties = append(allProperties, metadataProperties.Properties...) + + // Lastly re-marshal all the properties back into a JSON array and update the CSV annotation + allPropertiesJSON, err := json.Marshal(allProperties) + if err != nil { + return fmt.Errorf("failed to marshal registry+v1 properties to json: %w", err) + } + csv.Annotations["olm.properties"] = string(allPropertiesJSON) + return nil +} diff --git a/internal/operator-controller/rukpak/bundle/source/source_test.go b/internal/operator-controller/rukpak/bundle/source/source_test.go new file mode 100644 index 0000000000..45ccafd0dc --- /dev/null +++ b/internal/operator-controller/rukpak/bundle/source/source_test.go @@ -0,0 +1,93 @@ +package source_test + +import ( + "io/fs" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/bundlefs" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +const ( + olmProperties = "olm.properties" +) + +func Test_FromBundle_Success(t *testing.T) { + expectedBundle := bundle.RegistryV1{ + PackageName: "my-package", + } + b, err := source.FromBundle(expectedBundle).GetBundle() + require.NoError(t, err) + require.Equal(t, expectedBundle, b) +} + +func Test_FromFS_Success(t *testing.T) { + bundleFS := bundlefs.Builder(). + WithPackageName("test"). + WithBundleProperty("from-file-key", "from-file-value"). + WithBundleResource("csv.yaml", ptr.To(clusterserviceversion.Builder(). + WithName("test.v1.0.0"). + WithAnnotations(map[string]string{ + "olm.properties": `[{"type":"from-csv-annotations-key", "value":"from-csv-annotations-value"}]`, + }). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build())). + Build() + + rv1, err := source.FromFS(bundleFS).GetBundle() + require.NoError(t, err) + + t.Log("Check package name is correctly taken from metadata/annotations.yaml") + require.Equal(t, "test", rv1.PackageName) + + t.Log("Check metadata/properties.yaml is merged into csv.annotations[olm.properties]") + require.JSONEq(t, `[{"type":"from-csv-annotations-key","value":"from-csv-annotations-value"},{"type":"from-file-key","value":"from-file-value"}]`, rv1.CSV.Annotations[olmProperties]) +} + +func Test_FromFS_Fails(t *testing.T) { + for _, tt := range []struct { + name string + FS fs.FS + }{ + { + name: "bundle missing ClusterServiceVersion manifest", + FS: bundlefs.Builder(). + WithPackageName("test"). + WithBundleProperty("foo", "bar"). + WithBundleResource("service.yaml", &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + }).Build(), + }, { + name: "bundle missing metadata/annotations.yaml", + FS: bundlefs.Builder(). + WithBundleProperty("foo", "bar"). + WithBundleResource("csv.yaml", ptr.To(clusterserviceversion.Builder().Build())).Build(), + }, { + name: "metadata/annotations.yaml missing package name annotation", + FS: bundlefs.Builder(). + WithBundleProperty("foo", "bar"). + WithBundleResource("csv.yaml", ptr.To(clusterserviceversion.Builder().Build())).Build(), + }, { + name: "bundle missing manifests directory", + FS: bundlefs.Builder(). + WithPackageName("test"). + WithBundleProperty("foo", "bar").Build(), + }, + } { + t.Run(tt.name, func(t *testing.T) { + _, err := source.FromFS(tt.FS).GetBundle() + require.Error(t, err) + }) + } +} diff --git a/internal/rukpak/operator-registry/registry.go b/internal/operator-controller/rukpak/operator-registry/registry.go similarity index 100% rename from internal/rukpak/operator-registry/registry.go rename to internal/operator-controller/rukpak/operator-registry/registry.go diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go new file mode 100644 index 0000000000..e7830ce620 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go @@ -0,0 +1,331 @@ +package crdupgradesafety + +import ( + "context" + "errors" + "fmt" + "strings" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/crdify/pkg/config" + "sigs.k8s.io/crdify/pkg/runner" + "sigs.k8s.io/crdify/pkg/validations" + "sigs.k8s.io/crdify/pkg/validations/property" +) + +type Option func(p *Preflight) + +func WithConfig(cfg *config.Config) Option { + return func(p *Preflight) { + p.config = cfg + } +} + +func WithRegistry(reg validations.Registry) Option { + return func(p *Preflight) { + p.registry = reg + } +} + +type Preflight struct { + crdClient apiextensionsv1client.CustomResourceDefinitionInterface + config *config.Config + registry validations.Registry +} + +func NewPreflight(crdCli apiextensionsv1client.CustomResourceDefinitionInterface, opts ...Option) *Preflight { + p := &Preflight{ + crdClient: crdCli, + config: defaultConfig(), + registry: defaultRegistry(), + } + + for _, o := range opts { + o(p) + } + + return p +} + +func (p *Preflight) Install(ctx context.Context, objs []client.Object) error { + return p.runPreflight(ctx, objs) +} + +func (p *Preflight) Upgrade(ctx context.Context, objs []client.Object) error { + return p.runPreflight(ctx, objs) +} + +func (p *Preflight) runPreflight(ctx context.Context, relObjects []client.Object) error { + if len(relObjects) == 0 { + return nil + } + runner, err := runner.New(p.config, p.registry) + if err != nil { + return fmt.Errorf("creating CRD validation runner: %w", err) + } + + validateErrors := make([]error, 0, len(relObjects)) + for _, obj := range relObjects { + if obj.GetObjectKind().GroupVersionKind() != apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition") { + continue + } + + newCrd := &apiextensionsv1.CustomResourceDefinition{} + uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return fmt.Errorf("converting object %q to unstructured: %w", obj.GetName(), err) + } + err = runtime.DefaultUnstructuredConverter.FromUnstructured(uMap, newCrd) + if err != nil { + return fmt.Errorf("converting unstructured to CRD object: %w", err) + } + + oldCrd, err := p.crdClient.Get(ctx, newCrd.Name, metav1.GetOptions{}) + if err != nil { + // if there is no existing CRD, there is nothing to break + // so it is immediately successful. + if apierrors.IsNotFound(err) { + continue + } + return fmt.Errorf("getting existing resource for CRD %q: %w", newCrd.Name, err) + } + + results := runner.Run(oldCrd, newCrd) + if results.HasFailures() { + resultErrs := crdWideErrors(results) + resultErrs = append(resultErrs, sameVersionErrors(results)...) + resultErrs = append(resultErrs, servedVersionErrors(results)...) + if len(resultErrs) > 0 { + validateErrors = append(validateErrors, fmt.Errorf("validating upgrade for CRD %q: %w", newCrd.Name, errors.Join(resultErrs...))) + } + } + } + + return errors.Join(validateErrors...) +} + +func defaultConfig() *config.Config { + return &config.Config{ + // Ignore served version validations if conversion policy is set. + Conversion: config.ConversionPolicyIgnore, + // Fail-closed by default + UnhandledEnforcement: config.EnforcementPolicyError, + // Use the default validation configurations as they are + // the strictest possible. + Validations: []config.ValidationConfig{ + // Do not enforce the description validation + // because OLM should not block on field description changes. + { + Name: "description", + Enforcement: config.EnforcementPolicyNone, + }, + { + Name: "enum", + Enforcement: config.EnforcementPolicyError, + Configuration: map[string]interface{}{ + "additionPolicy": property.AdditionPolicyAllow, + }, + }, + }, + } +} + +func defaultRegistry() validations.Registry { + return runner.DefaultRegistry() +} + +func crdWideErrors(results *runner.Results) []error { + if results == nil { + return nil + } + + errs := []error{} + for _, result := range results.CRDValidation { + for _, err := range result.Errors { + errs = append(errs, fmt.Errorf("%s: %s", result.Name, err)) + } + } + + return errs +} + +func sameVersionErrors(results *runner.Results) []error { + if results == nil { + return nil + } + + errs := []error{} + for version, propertyResults := range results.SameVersionValidation { + for property, comparisonResults := range propertyResults { + for _, result := range comparisonResults { + for _, err := range result.Errors { + msg := err + if result.Name == "unhandled" { + msg = conciseUnhandledMessage(err) + } + errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, msg)) + } + } + } + } + + return errs +} + +func servedVersionErrors(results *runner.Results) []error { + if results == nil { + return nil + } + + errs := []error{} + for version, propertyResults := range results.ServedVersionValidation { + for property, comparisonResults := range propertyResults { + for _, result := range comparisonResults { + for _, err := range result.Errors { + msg := err + if result.Name == "unhandled" { + msg = conciseUnhandledMessage(err) + } + errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, msg)) + } + } + } + } + + return errs +} + +const unhandledSummaryPrefix = "unhandled changes found" + +// conciseUnhandledMessage trims the CRD diff emitted by crdify's "unhandled" comparator +// into a short human readable description so operators get a hint of the change without +// the unreadable Go struct dump. +func conciseUnhandledMessage(raw string) string { + if !strings.Contains(raw, unhandledSummaryPrefix) { + return raw + } + + details := extractUnhandledDetails(raw) + if len(details) == 0 { + return unhandledSummaryPrefix + } + + return fmt.Sprintf("%s (%s)", unhandledSummaryPrefix, strings.Join(details, "; ")) +} + +func extractUnhandledDetails(raw string) []string { + type diffEntry struct { + before string + after string + beforeRaw string + afterRaw string + } + + entries := map[string]*diffEntry{} + order := []string{} + + for _, line := range strings.Split(raw, "\n") { + trimmed := strings.TrimSpace(line) + if len(trimmed) < 2 { + continue + } + + sign := trimmed[0] + if sign != '-' && sign != '+' { + continue + } + + field, value, rawValue := parseUnhandledDiffValue(trimmed[1:]) + if field == "" { + continue + } + + entry, ok := entries[field] + if !ok { + entry = &diffEntry{} + entries[field] = entry + order = append(order, field) + } + + if sign == '-' { + entry.before = value + entry.beforeRaw = rawValue + } else { + entry.after = value + entry.afterRaw = rawValue + } + } + + details := []string{} + for _, field := range order { + entry := entries[field] + if entry.before == "" && entry.after == "" { + continue + } + if entry.before == entry.after && entry.beforeRaw == entry.afterRaw { + continue + } + + before := entry.before + if before == "" { + before = "" + } + after := entry.after + if after == "" { + after = "" + } + if entry.before == entry.after && entry.beforeRaw != entry.afterRaw { + after = after + " (changed)" + } + + details = append(details, fmt.Sprintf("%s %s -> %s", field, before, after)) + } + + return details +} + +func parseUnhandledDiffValue(fragment string) (string, string, string) { + cleaned := strings.TrimSpace(fragment) + cleaned = strings.TrimPrefix(cleaned, "\t") + cleaned = strings.TrimSpace(cleaned) + cleaned = strings.TrimSuffix(cleaned, ",") + + parts := strings.SplitN(cleaned, ":", 2) + if len(parts) != 2 { + return "", "", "" + } + + field := strings.TrimSpace(parts[0]) + rawValue := strings.TrimSpace(parts[1]) + value := normalizeUnhandledValue(rawValue) + + if field == "" { + return "", "", "" + } + + return field, value, rawValue +} + +func normalizeUnhandledValue(value string) string { + value = strings.TrimSuffix(value, ",") + value = strings.TrimSpace(value) + + switch value { + case "": + return "" + case "\"\"": + return "\"\"" + } + + value = strings.ReplaceAll(value, "v1.", "") + if strings.Contains(value, "JSONSchemaProps") { + return "" + } + + return value +} diff --git a/internal/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go similarity index 57% rename from internal/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go index 39e0a0fe94..9fb275880d 100644 --- a/internal/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - kappcus "carvel.dev/kapp/pkg/kapp/crdupgradesafety" "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/release" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -16,9 +15,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + crdifyconfig "sigs.k8s.io/crdify/pkg/config" - "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" - "github.com/operator-framework/operator-controller/internal/rukpak/util" + "github.com/operator-framework/operator-controller/internal/operator-controller/applier" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" ) type MockCRDGetter struct { @@ -31,18 +32,15 @@ func (c *MockCRDGetter) Get(ctx context.Context, name string, options metav1.Get return c.oldCrd, c.getErr } -func newMockPreflight(crd *apiextensionsv1.CustomResourceDefinition, err error, customValidator *kappcus.Validator) *crdupgradesafety.Preflight { +func newMockPreflight(crd *apiextensionsv1.CustomResourceDefinition, err error) *crdupgradesafety.Preflight { var preflightOpts []crdupgradesafety.Option - if customValidator != nil { - preflightOpts = append(preflightOpts, crdupgradesafety.WithValidator(customValidator)) - } return crdupgradesafety.NewPreflight(&MockCRDGetter{ oldCrd: crd, getErr: err, }, preflightOpts...) } -const crdFolder string = "../../../../testdata/manifests" +const crdFolder string = "testdata/manifests" func getCrdFromManifestFile(t *testing.T, oldCrdFile string) *apiextensionsv1.CustomResourceDefinition { if oldCrdFile == "" { @@ -70,15 +68,22 @@ func getManifestString(t *testing.T, crdFile string) string { return string(buff) } +func wantErrorMsgs(wantMsgs []string) require.ErrorAssertionFunc { + return func(t require.TestingT, haveErr error, _ ...interface{}) { + for _, wantMsg := range wantMsgs { + require.ErrorContains(t, haveErr, wantMsg) + } + } +} + // TestInstall exists only for completeness as Install() is currently a no-op. It can be used as // a template for real tests in the future if the func is implemented. func TestInstall(t *testing.T) { tests := []struct { name string oldCrdPath string - validator *kappcus.Validator release *release.Release - wantErrMsgs []string + requireErr require.ErrorAssertionFunc wantCrdGetErr error }{ { @@ -96,7 +101,7 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: "abcd", }, - wantErrMsgs: []string{"json: cannot unmarshal string into Go value of type unstructured.detector"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal string into Go value of type unstructured.detector"}), }, { name: "release with no CRD objects", @@ -112,7 +117,7 @@ func TestInstall(t *testing.T) { Manifest: getManifestString(t, "crd-valid-upgrade.json"), }, wantCrdGetErr: fmt.Errorf("error!"), - wantErrMsgs: []string{"error!"}, + requireErr: wantErrorMsgs([]string{"error!"}), }, { name: "fail to get old crd, not found error", @@ -128,23 +133,7 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid"), }, - wantErrMsgs: []string{"json: cannot unmarshal"}, - }, - { - name: "custom validator", - oldCrdPath: "old-crd.json", - release: &release.Release{ - Name: "test-release", - Manifest: getManifestString(t, "old-crd.json"), - }, - validator: &kappcus.Validator{ - Validations: []kappcus.Validation{ - kappcus.NewValidationFunc("test", func(old, new apiextensionsv1.CustomResourceDefinition) error { - return fmt.Errorf("custom validation error!!") - }), - }, - }, - wantErrMsgs: []string{"custom validation error!!"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal"}), }, { name: "valid upgrade", @@ -163,21 +152,21 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid-upgrade.json"), }, - wantErrMsgs: []string{ - `"NoScopeChange"`, - `"NoStoredVersionRemoved"`, - `enums added`, - `new required fields added`, - `maximum constraint added when one did not exist previously`, - `maximum items constraint added`, - `maximum length constraint added`, - `maximum properties constraint added`, - `minimum constraint added when one did not exist previously`, - `minimum items constraint added`, - `minimum length constraint added`, - `minimum properties constraint added`, - `new value added as default`, - }, + requireErr: wantErrorMsgs([]string{ + `scope:`, + `storedVersionRemoval:`, + `enum:`, + `required:`, + `maximum:`, + `maxItems:`, + `maxLength:`, + `maxProperties:`, + `minimum:`, + `minItems:`, + `minLength:`, + `minProperties:`, + `default:`, + }), }, { name: "new crd validation failure for existing field removal", @@ -188,20 +177,31 @@ func TestInstall(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-field-removed.json"), }, - wantErrMsgs: []string{ - `"NoExistingFieldRemoved"`, + requireErr: wantErrorMsgs([]string{ + `existingFieldRemoval:`, + }), + }, + { + name: "new crd validation should not fail on description changes", + // Separate test from above as this error will cause the validator to + // return early and skip some of the above validations. + oldCrdPath: "old-crd.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-description-changed.json"), }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr, tc.validator) - err := preflight.Install(context.Background(), tc.release) - if len(tc.wantErrMsgs) != 0 { - for _, expectedErrMsg := range tc.wantErrMsgs { - require.ErrorContainsf(t, err, expectedErrMsg, "") - } + preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr) + objs, err := applier.HelmReleaseToObjectsConverter{}.GetObjectsFromRelease(tc.release) + if err == nil { + err = preflight.Install(context.Background(), objs) + } + if tc.requireErr != nil { + tc.requireErr(t, err) } else { require.NoError(t, err) } @@ -213,9 +213,8 @@ func TestUpgrade(t *testing.T) { tests := []struct { name string oldCrdPath string - validator *kappcus.Validator release *release.Release - wantErrMsgs []string + requireErr require.ErrorAssertionFunc wantCrdGetErr error }{ { @@ -233,7 +232,7 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: "abcd", }, - wantErrMsgs: []string{"json: cannot unmarshal string into Go value of type unstructured.detector"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal string into Go value of type unstructured.detector"}), }, { name: "release with no CRD objects", @@ -249,7 +248,7 @@ func TestUpgrade(t *testing.T) { Manifest: getManifestString(t, "crd-valid-upgrade.json"), }, wantCrdGetErr: fmt.Errorf("error!"), - wantErrMsgs: []string{"error!"}, + requireErr: wantErrorMsgs([]string{"error!"}), }, { name: "fail to get old crd, not found error", @@ -265,23 +264,7 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid"), }, - wantErrMsgs: []string{"json: cannot unmarshal"}, - }, - { - name: "custom validator", - oldCrdPath: "old-crd.json", - release: &release.Release{ - Name: "test-release", - Manifest: getManifestString(t, "old-crd.json"), - }, - validator: &kappcus.Validator{ - Validations: []kappcus.Validation{ - kappcus.NewValidationFunc("test", func(old, new apiextensionsv1.CustomResourceDefinition) error { - return fmt.Errorf("custom validation error!!") - }), - }, - }, - wantErrMsgs: []string{"custom validation error!!"}, + requireErr: wantErrorMsgs([]string{"json: cannot unmarshal"}), }, { name: "valid upgrade", @@ -300,21 +283,21 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-invalid-upgrade.json"), }, - wantErrMsgs: []string{ - `"NoScopeChange"`, - `"NoStoredVersionRemoved"`, - `enums added`, - `new required fields added`, - `maximum constraint added when one did not exist previously`, - `maximum items constraint added`, - `maximum length constraint added`, - `maximum properties constraint added`, - `minimum constraint added when one did not exist previously`, - `minimum items constraint added`, - `minimum length constraint added`, - `minimum properties constraint added`, - `new value added as default`, - }, + requireErr: wantErrorMsgs([]string{ + `scope:`, + `storedVersionRemoval:`, + `enum:`, + `required:`, + `maximum:`, + `maxItems:`, + `maxLength:`, + `maxProperties:`, + `minimum:`, + `minItems:`, + `minLength:`, + `minProperties:`, + `default:`, + }), }, { name: "new crd validation failure for existing field removal", @@ -325,9 +308,9 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-field-removed.json"), }, - wantErrMsgs: []string{ - `"NoExistingFieldRemoved"`, - }, + requireErr: wantErrorMsgs([]string{ + `existingFieldRemoval:`, + }), }, { name: "webhook conversion strategy exists", @@ -344,23 +327,102 @@ func TestUpgrade(t *testing.T) { Name: "test-release", Manifest: getManifestString(t, "crd-conversion-no-webhook.json"), }, - wantErrMsgs: []string{ - `"ServedVersionValidator" validation failed: version upgrade "v1" to "v2", field "^.spec.foobarbaz": enum values removed`, + requireErr: wantErrorMsgs([]string{ + `validating upgrade for CRD "crontabs.stable.example.com": v1 -> v2: ^.spec.foobarbaz: enum: allowed enum values removed`, + }), + }, + { + name: "new crd validation should not fail on description changes", + // Separate test from above as this error will cause the validator to + // return early and skip some of the above validations. + oldCrdPath: "old-crd.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-description-changed.json"), + }, + }, + { + name: "success when old crd and new crd contain the exact same validation issues", + oldCrdPath: "crd-conversion-no-webhook.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-conversion-no-webhook.json"), + }, + }, + { + name: "failure when old crd and new crd contain the exact same validation issues, but new crd introduces another validation issue", + oldCrdPath: "crd-conversion-no-webhook.json", + release: &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-conversion-no-webhook-extra-issue.json"), + }, + requireErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorContains(t, err, + `validating upgrade for CRD "crontabs.stable.example.com":`, + ) + // The newly introduced issue is reported + require.Contains(t, err.Error(), + `v1 -> v2: ^.spec.extraField: type: type changed : "boolean" -> "string"`, + ) + // The existing issue is not reported + require.NotContains(t, err.Error(), + `v1 -> v2: ^.spec.foobarbaz: enum: allowed enum values removed`, + ) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr, tc.validator) - err := preflight.Upgrade(context.Background(), tc.release) - if len(tc.wantErrMsgs) != 0 { - for _, expectedErrMsg := range tc.wantErrMsgs { - require.ErrorContainsf(t, err, expectedErrMsg, "") - } + preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr) + objs, err := applier.HelmReleaseToObjectsConverter{}.GetObjectsFromRelease(tc.release) + if err == nil { + err = preflight.Upgrade(context.Background(), objs) + } + if tc.requireErr != nil { + tc.requireErr(t, err) } else { require.NoError(t, err) } }) } } + +func TestUpgrade_UnhandledChanges_InSpec_DefaultPolicy(t *testing.T) { + t.Run("unhandled spec changes cause error by default", func(t *testing.T) { + preflight := newMockPreflight(getCrdFromManifestFile(t, "crd-unhandled-old.json"), nil) + rel := &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-unhandled-new.json"), + } + objs, err := applier.HelmReleaseToObjectsConverter{}.GetObjectsFromRelease(rel) + require.NoError(t, err) + err = preflight.Upgrade(context.Background(), objs) + require.Error(t, err) + require.ErrorContains(t, err, "unhandled changes found") + require.ErrorContains(t, err, "Format \"\" -> \"email\"") + require.NotContains(t, err.Error(), "v1.JSONSchemaProps", "error message should be concise without raw diff") + }) +} + +func TestUpgrade_UnhandledChanges_PolicyError(t *testing.T) { + t.Run("unhandled changes error when policy is Error", func(t *testing.T) { + oldCrd := getCrdFromManifestFile(t, "crd-unhandled-old.json") + preflight := crdupgradesafety.NewPreflight(&MockCRDGetter{oldCrd: oldCrd}, crdupgradesafety.WithConfig(&crdifyconfig.Config{ + Conversion: crdifyconfig.ConversionPolicyIgnore, + UnhandledEnforcement: crdifyconfig.EnforcementPolicyError, + })) + + rel := &release.Release{ + Name: "test-release", + Manifest: getManifestString(t, "crd-unhandled-new.json"), + } + + objs, err := applier.HelmReleaseToObjectsConverter{}.GetObjectsFromRelease(rel) + require.NoError(t, err) + err = preflight.Upgrade(context.Background(), objs) + require.Error(t, err) + require.ErrorContains(t, err, "unhandled changes found") + require.ErrorContains(t, err, "Format \"\" -> \"email\"") + }) +} diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json new file mode 100644 index 0000000000..0bfd133843 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook-extra-issue.json @@ -0,0 +1,76 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "crontabs.stable.example.com" + }, + "spec": { + "group": "stable.example.com", + "versions": [ + { + "name": "v2", + "served": true, + "storage": false, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "foobarbaz": { + "type":"string", + "enum":[ + "bark", + "woof" + ] + }, + "extraField": { + "type":"string" + } + } + } + } + } + } + }, + { + "name": "v1", + "served": true, + "storage": false, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "foobarbaz": { + "type":"string", + "enum":[ + "foo", + "bar", + "baz" + ] + }, + "extraField": { + "type":"boolean" + } + } + } + } + } + } + } + ], + "scope": "Cluster", + "names": { + "plural": "crontabs", + "singular": "crontab", + "kind": "CronTab", + "shortNames": [ + "ct" + ] + } + } +} diff --git a/testdata/manifests/crd-conversion-no-webhook.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook.json similarity index 100% rename from testdata/manifests/crd-conversion-no-webhook.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-no-webhook.json diff --git a/testdata/manifests/crd-conversion-webhook-old.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook-old.json similarity index 100% rename from testdata/manifests/crd-conversion-webhook-old.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook-old.json diff --git a/testdata/manifests/crd-conversion-webhook.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook.json similarity index 100% rename from testdata/manifests/crd-conversion-webhook.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-conversion-webhook.json diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json new file mode 100644 index 0000000000..0e7f9a600b --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-description-changed.json @@ -0,0 +1,124 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "crontabs.stable.example.com" + }, + "spec": { + "group": "stable.example.com", + "versions": [ + { + "name": "v1", + "served": true, + "storage": false, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "description": "description two", + "type": "object", + "properties": { + "removedField": { + "type":"integer" + }, + "enum": { + "type": "string", + "enum": ["a", "b", "c"] + }, + "minMaxValue": { + "type":"integer" + }, + "required": { + "type":"integer" + }, + "minMaxItems": { + "type":"array", + "items": { + "type":"string" + } + }, + "minMaxLength": { + "type":"string" + }, + "defaultVal": { + "type": "string" + }, + "requiredVal": { + "type": "string" + } + } + } + }, + "required": [ + "requiredVal" + ] + } + } + }, + { + "name": "v2", + "served": true, + "storage": true, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "removedField": { + "type":"integer" + }, + "enum": { + "type": "string", + "enum": ["a", "b", "c"] + }, + "minMaxValue": { + "type":"integer" + }, + "required": { + "type":"integer" + }, + "minMaxItems": { + "type":"array", + "items": { + "type":"string" + } + }, + "minMaxLength": { + "type":"string" + }, + "defaultVal": { + "type": "string" + }, + "requiredVal": { + "type": "string" + } + } + } + }, + "required": [ + "requiredVal" + ] + } + } + } + ], + "scope": "Cluster", + "names": { + "plural": "crontabs", + "singular": "crontab", + "kind": "CronTab", + "shortNames": [ + "ct" + ] + } + }, + "status": { + "storedVersions": [ + "v1", + "v2" + ] + } +} diff --git a/testdata/manifests/crd-field-removed.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json similarity index 96% rename from testdata/manifests/crd-field-removed.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json index 86ba06e409..650b13fd48 100644 --- a/testdata/manifests/crd-field-removed.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-field-removed.json @@ -22,7 +22,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" @@ -66,7 +67,8 @@ "type": "object", "properties": { "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" diff --git a/testdata/manifests/crd-invalid b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid similarity index 100% rename from testdata/manifests/crd-invalid rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid diff --git a/testdata/manifests/crd-invalid-upgrade.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json similarity index 92% rename from testdata/manifests/crd-invalid-upgrade.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json index 4131a68fbf..3c95ccb25a 100644 --- a/testdata/manifests/crd-invalid-upgrade.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-invalid-upgrade.json @@ -24,11 +24,8 @@ "type":"integer" }, "enum": { - "type":"integer", - "enum":[ - 1, - 2 - ] + "type": "string", + "enum": ["a", "b"] }, "minMaxValue": { "type":"integer", diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-new.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-new.json new file mode 100644 index 0000000000..6fed77fc16 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-new.json @@ -0,0 +1,40 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "widgets.example.com" + }, + "spec": { + "group": "example.com", + "versions": [ + { + "name": "v1alpha1", + "served": true, + "storage": true, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "field": { + "type": "string", + "format": "email" + } + } + } + } + } + } + } + ], + "scope": "Namespaced", + "names": { + "plural": "widgets", + "singular": "widget", + "kind": "Widget" + } + } +} + diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-old.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-old.json new file mode 100644 index 0000000000..a87fbd5054 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-unhandled-old.json @@ -0,0 +1,39 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "widgets.example.com" + }, + "spec": { + "group": "example.com", + "versions": [ + { + "name": "v1alpha1", + "served": true, + "storage": true, + "schema": { + "openAPIV3Schema": { + "type": "object", + "properties": { + "spec": { + "type": "object", + "properties": { + "field": { + "type": "string" + } + } + } + } + } + } + } + ], + "scope": "Namespaced", + "names": { + "plural": "widgets", + "singular": "widget", + "kind": "Widget" + } + } +} + diff --git a/testdata/manifests/crd-valid-upgrade.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json similarity index 93% rename from testdata/manifests/crd-valid-upgrade.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json index 52380dc92b..cbc2e3ec14 100644 --- a/testdata/manifests/crd-valid-upgrade.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/crd-valid-upgrade.json @@ -22,7 +22,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" @@ -69,7 +70,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" @@ -116,7 +118,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c", "adding-enum-is-allowed"] }, "minMaxValue": { "type":"integer" diff --git a/testdata/manifests/no-crds.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/no-crds.json similarity index 78% rename from testdata/manifests/no-crds.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/no-crds.json index a4cf5505b0..bdfca9daa5 100644 --- a/testdata/manifests/no-crds.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/no-crds.json @@ -5,10 +5,10 @@ "creationTimestamp": null, "labels": { "app.kubernetes.io/component": "controller", - "app.kubernetes.io/name": "prometheus-operator", + "app.kubernetes.io/name": "test-operator", "app.kubernetes.io/version": "1.0.0" }, - "name": "prometheus-operator" + "name": "test-operator" }, "spec": { "clusterIP": "None", @@ -21,7 +21,7 @@ ], "selector": { "app.kubernetes.io/component": "controller", - "app.kubernetes.io/name": "prometheus-operator" + "app.kubernetes.io/name": "test-operator" } }, "status": { diff --git a/testdata/manifests/old-crd.json b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json similarity index 93% rename from testdata/manifests/old-crd.json rename to internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json index 08e7634518..5a8c55b321 100644 --- a/testdata/manifests/old-crd.json +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/testdata/manifests/old-crd.json @@ -16,13 +16,15 @@ "type": "object", "properties": { "spec": { + "description": "description one", "type": "object", "properties": { "removedField": { "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" @@ -69,7 +71,8 @@ "type":"integer" }, "enum": { - "type":"integer" + "type": "string", + "enum": ["a", "b", "c"] }, "minMaxValue": { "type":"integer" diff --git a/internal/operator-controller/rukpak/preflights/crdupgradesafety/unhandled_message_test.go b/internal/operator-controller/rukpak/preflights/crdupgradesafety/unhandled_message_test.go new file mode 100644 index 0000000000..59078655a0 --- /dev/null +++ b/internal/operator-controller/rukpak/preflights/crdupgradesafety/unhandled_message_test.go @@ -0,0 +1,28 @@ +package crdupgradesafety + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConciseUnhandledMessage_NoPrefix(t *testing.T) { + raw := "some other error" + require.Equal(t, raw, conciseUnhandledMessage(raw)) +} + +func TestConciseUnhandledMessage_SingleChange(t *testing.T) { + raw := "unhandled changes found :\n- Format: \"\"\n+ Format: \"email\"\n" + require.Equal(t, "unhandled changes found (Format \"\" -> \"email\")", conciseUnhandledMessage(raw)) +} + +func TestConciseUnhandledMessage_MultipleChanges(t *testing.T) { + raw := "unhandled changes found :\n- Format: \"\"\n+ Format: \"email\"\n- Default: nil\n+ Default: \"value\"\n- Title: \"\"\n+ Title: \"Widget\"\n- Description: \"old\"\n+ Description: \"new\"\n" + got := conciseUnhandledMessage(raw) + require.Equal(t, "unhandled changes found (Format \"\" -> \"email\"; Default nil -> \"value\"; Title \"\" -> \"Widget\"; Description \"old\" -> \"new\")", got) +} + +func TestConciseUnhandledMessage_SkipComplexValues(t *testing.T) { + raw := "unhandled changes found :\n- Default: &v1.JSONSchemaProps{}\n+ Default: &v1.JSONSchemaProps{Type: \"string\"}\n" + require.Equal(t, "unhandled changes found (Default -> (changed))", conciseUnhandledMessage(raw)) +} diff --git a/internal/operator-controller/rukpak/render/certprovider.go b/internal/operator-controller/rukpak/render/certprovider.go new file mode 100644 index 0000000000..80c9e67ad3 --- /dev/null +++ b/internal/operator-controller/rukpak/render/certprovider.go @@ -0,0 +1,78 @@ +package render + +import ( + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +// CertificateProvider encapsulate the creation and modification of object for certificate provisioning +// in Kubernetes by vendors such as CertManager or the OpenshiftServiceCA operator +type CertificateProvider interface { + InjectCABundle(obj client.Object, cfg CertificateProvisionerConfig) error + AdditionalObjects(cfg CertificateProvisionerConfig) ([]unstructured.Unstructured, error) + GetCertSecretInfo(cfg CertificateProvisionerConfig) CertSecretInfo +} + +// CertSecretInfo contains describes the certificate secret resource information such as name and +// certificate and private key keys +type CertSecretInfo struct { + SecretName string + CertificateKey string + PrivateKeyKey string +} + +// CertificateProvisionerConfig contains the necessary information for a CertificateProvider +// to correctly generate and modify object for certificate injection and automation +type CertificateProvisionerConfig struct { + ServiceName string + CertName string + Namespace string + CertProvider CertificateProvider +} + +// CertificateProvisioner uses a CertificateProvider to modify and generate objects based on its +// CertificateProvisionerConfig +type CertificateProvisioner CertificateProvisionerConfig + +func (c CertificateProvisioner) InjectCABundle(obj client.Object) error { + if c.CertProvider == nil { + return nil + } + return c.CertProvider.InjectCABundle(obj, CertificateProvisionerConfig(c)) +} + +func (c CertificateProvisioner) AdditionalObjects() ([]unstructured.Unstructured, error) { + if c.CertProvider == nil { + return nil, nil + } + return c.CertProvider.AdditionalObjects(CertificateProvisionerConfig(c)) +} + +func (c CertificateProvisioner) GetCertSecretInfo() *CertSecretInfo { + if c.CertProvider == nil { + return nil + } + info := c.CertProvider.GetCertSecretInfo(CertificateProvisionerConfig(c)) + return &info +} + +func CertProvisionerFor(deploymentName string, opts Options) CertificateProvisioner { + // maintaining parity with OLMv0 naming + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/658a6a60de8315f055f54aa7e50771ee4daa8983/pkg/controller/install/webhook.go#L254 + webhookServiceName := util.ObjectNameForBaseAndSuffix(strings.ReplaceAll(deploymentName, ".", "-"), "service") + + // maintaining parity with cert secret name in OLMv0 + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/658a6a60de8315f055f54aa7e50771ee4daa8983/pkg/controller/install/certresources.go#L151 + certName := util.ObjectNameForBaseAndSuffix(webhookServiceName, "cert") + + return CertificateProvisioner{ + CertProvider: opts.CertificateProvider, + ServiceName: webhookServiceName, + Namespace: opts.InstallNamespace, + CertName: certName, + } +} diff --git a/internal/operator-controller/rukpak/render/certprovider_test.go b/internal/operator-controller/rukpak/render/certprovider_test.go new file mode 100644 index 0000000000..a245a4173a --- /dev/null +++ b/internal/operator-controller/rukpak/render/certprovider_test.go @@ -0,0 +1,122 @@ +package render_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + . "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing" +) + +func Test_CertificateProvisioner_WithoutCertProvider(t *testing.T) { + provisioner := &render.CertificateProvisioner{ + ServiceName: "webhook", + CertName: "cert", + Namespace: "namespace", + CertProvider: nil, + } + + require.NoError(t, provisioner.InjectCABundle(&corev1.Secret{})) + require.Nil(t, provisioner.GetCertSecretInfo()) + + objs, err := provisioner.AdditionalObjects() + require.Nil(t, objs) + require.NoError(t, err) +} + +func Test_CertificateProvisioner_WithCertProvider(t *testing.T) { + fakeProvider := &FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + obj.SetName("some-name") + return nil + }, + AdditionalObjectsFn: func(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return []unstructured.Unstructured{*ToUnstructuredT(t, &corev1.Secret{ + TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}, + })}, nil + }, + GetCertSecretInfoFn: func(cfg render.CertificateProvisionerConfig) render.CertSecretInfo { + return render.CertSecretInfo{ + SecretName: "some-secret", + PrivateKeyKey: "some-key", + CertificateKey: "another-key", + } + }, + } + provisioner := &render.CertificateProvisioner{ + ServiceName: "webhook", + CertName: "cert", + Namespace: "namespace", + CertProvider: fakeProvider, + } + + svc := &corev1.Service{} + require.NoError(t, provisioner.InjectCABundle(svc)) + require.Equal(t, "some-name", svc.GetName()) + + objs, err := provisioner.AdditionalObjects() + require.NoError(t, err) + require.Equal(t, []unstructured.Unstructured{*ToUnstructuredT(t, &corev1.Secret{ + TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}, + })}, objs) + + require.Equal(t, &render.CertSecretInfo{ + SecretName: "some-secret", + PrivateKeyKey: "some-key", + CertificateKey: "another-key", + }, provisioner.GetCertSecretInfo()) +} + +func Test_CertificateProvisioner_Errors(t *testing.T) { + fakeProvider := &FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + return fmt.Errorf("some error") + }, + AdditionalObjectsFn: func(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return nil, fmt.Errorf("some other error") + }, + } + provisioner := &render.CertificateProvisioner{ + ServiceName: "webhook", + CertName: "cert", + Namespace: "namespace", + CertProvider: fakeProvider, + } + + err := provisioner.InjectCABundle(&corev1.Service{}) + require.Error(t, err) + require.Contains(t, err.Error(), "some error") + + objs, err := provisioner.AdditionalObjects() + require.Error(t, err) + require.Contains(t, err.Error(), "some other error") + require.Nil(t, objs) +} + +func Test_CertProvisionerFor(t *testing.T) { + fakeProvider := &FakeCertProvider{} + prov := render.CertProvisionerFor("my.deployment.thing", render.Options{ + InstallNamespace: "my-namespace", + CertificateProvider: fakeProvider, + }) + + require.Equal(t, prov.CertProvider, fakeProvider) + require.Equal(t, "my-deployment-thing-service", prov.ServiceName) + require.Equal(t, "my-deployment-thing-service-cert", prov.CertName) + require.Equal(t, "my-namespace", prov.Namespace) +} + +func Test_CertProvisionerFor_ExtraLargeName_MoreThan63Chars(t *testing.T) { + prov := render.CertProvisionerFor("my.object.thing.has.a.really.really.really.really.really.long.name", render.Options{}) + + require.Len(t, prov.ServiceName, 63) + require.Len(t, prov.CertName, 63) + require.Equal(t, "my-object-thing-has-a-really-really-really-really-reall-service", prov.ServiceName) + require.Equal(t, "my-object-thing-has-a-really-really-really-really-reall-se-cert", prov.CertName) +} diff --git a/internal/operator-controller/rukpak/render/certproviders/certmanager.go b/internal/operator-controller/rukpak/render/certproviders/certmanager.go new file mode 100644 index 0000000000..0cde052c7f --- /dev/null +++ b/internal/operator-controller/rukpak/render/certproviders/certmanager.go @@ -0,0 +1,193 @@ +package certproviders + +import ( + "errors" + "fmt" + "time" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +const ( + certManagerInjectCAAnnotation = "cert-manager.io/inject-ca-from" + olmv0RotationPeriod = 730 * 24 * time.Hour // 2 year rotation + olmv0RenewBefore = 24 * time.Hour // renew certificate within 24h of expiry +) + +var _ render.CertificateProvider = (*CertManagerCertificateProvider)(nil) + +type CertManagerCertificateProvider struct{} + +func (p CertManagerCertificateProvider) InjectCABundle(obj client.Object, cfg render.CertificateProvisionerConfig) error { + switch obj.(type) { + case *admissionregistrationv1.ValidatingWebhookConfiguration: + p.addCAInjectionAnnotation(obj, cfg.Namespace, cfg.CertName) + case *admissionregistrationv1.MutatingWebhookConfiguration: + p.addCAInjectionAnnotation(obj, cfg.Namespace, cfg.CertName) + case *apiextensionsv1.CustomResourceDefinition: + p.addCAInjectionAnnotation(obj, cfg.Namespace, cfg.CertName) + } + return nil +} + +func (p CertManagerCertificateProvider) GetCertSecretInfo(cfg render.CertificateProvisionerConfig) render.CertSecretInfo { + return render.CertSecretInfo{ + SecretName: cfg.CertName, + PrivateKeyKey: "tls.key", + CertificateKey: "tls.crt", + } +} + +func (p CertManagerCertificateProvider) AdditionalObjects(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + var ( + objs []unstructured.Unstructured + errs []error + ) + + // OLMv0 parity: + // - self-signed issuer + // - 2 year rotation period + // - renew 24h before expiry + // - CN: argocd-operator-controller-manager-service.argocd (-service.) + // - CA: false + // - DNS:argocd-operator-controller-manager-service.argocd, DNS:argocd-operator-controller-manager-service.argocd.svc, DNS:argocd-operator-controller-manager-service.argocd.svc.cluster.local + + // Full example of OLMv0 Certificate data (argocd-operator.v0.8.0): + //Certificate: + // Data: + // Version: 3 (0x2) + // Serial Number: 1507821748758744637 (0x14ecdbe4475f8e3d) + // Signature Algorithm: ecdsa-with-SHA256 + // Issuer: O=Red Hat, Inc., CN=olm-selfsigned-275dd2a363db7513 + // Validity + // Not Before: May 12 11:15:02 2025 GMT + // Not After : May 12 11:15:02 2027 GMT + // Subject: O=Red Hat, Inc., CN=argocd-operator-controller-manager-service.argocd + // Subject Public Key Info: + // Public Key Algorithm: id-ecPublicKey + // Public-Key: (256 bit) + // pub: ... + // ASN1 OID: prime256v1 + // NIST CURVE: P-256 + // X509v3 extensions: + // X509v3 Extended Key Usage: + // TLS Web Server Authentication + // X509v3 Basic Constraints: critical + // CA:FALSE + // X509v3 Authority Key Identifier: ... + // X509v3 Subject Alternative Name: + // DNS:argocd-operator-controller-manager-service.argocd, DNS:argocd-operator-controller-manager-service.argocd.svc, DNS:argocd-operator-controller-manager-service.argocd.svc.cluster.local + // Signature Algorithm: ecdsa-with-SHA256 + // Signature Value: ... + + // Full example of OLMv1 certificate for argocd-operator v0.8.0 with the Issuer and Certificate settings that follow: + //Certificate: + // Data: + // Version: 3 (0x2) + // Serial Number: + // d5:8f:4f:ae:b1:67:59:9d:fe:53:b5:41:d3:10:5a:2b + // Signature Algorithm: sha256WithRSAEncryption + // Issuer: CN=argocd-operator-controller-manager-service.argocd + // Validity + // Not Before: May 12 11:55:28 2025 GMT + // Not After : May 12 11:55:28 2027 GMT + // Subject: CN=argocd-operator-controller-manager-service.argocd + // Subject Public Key Info: + // Public Key Algorithm: rsaEncryption + // Public-Key: (2048 bit) + // Modulus: ... + // Exponent: 65537 (0x10001) + // X509v3 extensions: + // X509v3 Extended Key Usage: + // TLS Web Server Authentication + // X509v3 Basic Constraints: critical + // CA:FALSE + // X509v3 Subject Alternative Name: + // DNS:argocd-operator-controller-manager-service.argocd, DNS:argocd-operator-controller-manager-service.argocd.svc, DNS:argocd-operator-controller-manager-service.argocd.svc.cluster.local + // Signature Algorithm: sha256WithRSAEncryption + // Signature Value: ... + + // Notes: + // - the Organization "Red Hat, Inc." will not be used to avoid any hard links between Red Hat and the operator-controller project + // - for OLMv1 we'll use the default algorithm settings and key size (2048) coming from cert-manager as this is deemed more secure + + issuer := &certmanagerv1.Issuer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: certmanagerv1.SchemeGroupVersion.String(), + Kind: "Issuer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: util.ObjectNameForBaseAndSuffix(cfg.CertName, "selfsigned-issuer"), + Namespace: cfg.Namespace, + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + SelfSigned: &certmanagerv1.SelfSignedIssuer{}, + }, + }, + } + issuerObj, err := util.ToUnstructured(issuer) + if err != nil { + errs = append(errs, err) + } else { + objs = append(objs, *issuerObj) + } + + certificate := &certmanagerv1.Certificate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: certmanagerv1.SchemeGroupVersion.String(), + Kind: "Certificate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cfg.CertName, + Namespace: cfg.Namespace, + }, + Spec: certmanagerv1.CertificateSpec{ + SecretName: cfg.CertName, + CommonName: fmt.Sprintf("%s.%s", cfg.ServiceName, cfg.Namespace), + Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageServerAuth}, + IsCA: false, + DNSNames: []string{ + fmt.Sprintf("%s.%s", cfg.ServiceName, cfg.Namespace), + fmt.Sprintf("%s.%s.svc", cfg.ServiceName, cfg.Namespace), + fmt.Sprintf("%s.%s.svc.cluster.local", cfg.ServiceName, cfg.Namespace), + }, + IssuerRef: certmanagermetav1.ObjectReference{ + Name: issuer.GetName(), + }, + Duration: &metav1.Duration{ + Duration: olmv0RotationPeriod, + }, + RenewBefore: &metav1.Duration{ + Duration: olmv0RenewBefore, + }, + }, + } + certObj, err := util.ToUnstructured(certificate) + if err != nil { + errs = append(errs, err) + } else { + objs = append(objs, *certObj) + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return objs, nil +} + +func (p CertManagerCertificateProvider) addCAInjectionAnnotation(obj client.Object, certNamespace string, certName string) { + injectionAnnotation := map[string]string{ + certManagerInjectCAAnnotation: fmt.Sprintf("%s/%s", certNamespace, certName), + } + obj.SetAnnotations(util.MergeMaps(obj.GetAnnotations(), injectionAnnotation)) +} diff --git a/internal/operator-controller/rukpak/render/certproviders/certmanager_test.go b/internal/operator-controller/rukpak/render/certproviders/certmanager_test.go new file mode 100644 index 0000000000..2acb40d729 --- /dev/null +++ b/internal/operator-controller/rukpak/render/certproviders/certmanager_test.go @@ -0,0 +1,173 @@ +package certproviders_test + +import ( + "testing" + "time" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/stretchr/testify/require" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/certproviders" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +func Test_CertManagerProvider_InjectCABundle(t *testing.T) { + for _, tc := range []struct { + name string + obj client.Object + cfg render.CertificateProvisionerConfig + expectedObj client.Object + }{ + { + name: "injects certificate annotation in validating webhook configuration", + obj: &admissionregistrationv1.ValidatingWebhookConfiguration{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "cert-manager.io/inject-ca-from": "namespace/cert-name", + }, + }, + }, + }, + { + name: "injects certificate annotation in mutating webhook configuration", + obj: &admissionregistrationv1.MutatingWebhookConfiguration{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "cert-manager.io/inject-ca-from": "namespace/cert-name", + }, + }, + }, + }, + { + name: "injects certificate annotation in custom resource definition", + obj: &apiextensionsv1.CustomResourceDefinition{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "cert-manager.io/inject-ca-from": "namespace/cert-name", + }, + }, + }, + }, + { + name: "ignores other objects", + obj: &corev1.Service{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &corev1.Service{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + certProvier := certproviders.CertManagerCertificateProvider{} + require.NoError(t, certProvier.InjectCABundle(tc.obj, tc.cfg)) + require.Equal(t, tc.expectedObj, tc.obj) + }) + } +} + +func Test_CertManagerProvider_AdditionalObjects(t *testing.T) { + certProvier := certproviders.CertManagerCertificateProvider{} + objs, err := certProvier.AdditionalObjects(render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }) + require.NoError(t, err) + require.Equal(t, []unstructured.Unstructured{ + toUnstructured(t, &certmanagerv1.Issuer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: certmanagerv1.SchemeGroupVersion.String(), + Kind: "Issuer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-name-selfsigned-issuer", + Namespace: "namespace", + }, + Spec: certmanagerv1.IssuerSpec{ + IssuerConfig: certmanagerv1.IssuerConfig{ + SelfSigned: &certmanagerv1.SelfSignedIssuer{}, + }, + }, + }), + toUnstructured(t, &certmanagerv1.Certificate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: certmanagerv1.SchemeGroupVersion.String(), + Kind: "Certificate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-name", + Namespace: "namespace", + }, + Spec: certmanagerv1.CertificateSpec{ + SecretName: "cert-name", + Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageServerAuth}, + CommonName: "webhook-service.namespace", + IsCA: false, + DNSNames: []string{ + "webhook-service.namespace", + "webhook-service.namespace.svc", + "webhook-service.namespace.svc.cluster.local", + }, + IssuerRef: certmanagermetav1.ObjectReference{ + Name: "cert-name-selfsigned-issuer", + }, + Duration: &metav1.Duration{ + // OLMv0 has a 2 year certificate rotation period + Duration: 730 * 24 * time.Hour, + }, + RenewBefore: &metav1.Duration{ + // OLMv0 reviews 24h before expiry + Duration: 24 * time.Hour, + }, + }, + }), + }, objs) +} + +func Test_CertManagerProvider_GetCertSecretInfo(t *testing.T) { + certProvier := certproviders.CertManagerCertificateProvider{} + certInfo := certProvier.GetCertSecretInfo(render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }) + require.Equal(t, render.CertSecretInfo{ + SecretName: "cert-name", + PrivateKeyKey: "tls.key", + CertificateKey: "tls.crt", + }, certInfo) +} + +func toUnstructured(t *testing.T, obj client.Object) unstructured.Unstructured { + u, err := util.ToUnstructured(obj) + require.NoError(t, err) + return *u +} diff --git a/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca.go b/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca.go new file mode 100644 index 0000000000..5a1c72cc20 --- /dev/null +++ b/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca.go @@ -0,0 +1,61 @@ +package certproviders + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +const ( + openshiftServiceCAServingCertNameAnnotation = "service.beta.openshift.io/serving-cert-secret-name" + openshiftServiceCAInjectCABundleAnnotation = "service.beta.openshift.io/inject-cabundle" +) + +var _ render.CertificateProvider = (*OpenshiftServiceCaCertificateProvider)(nil) + +type OpenshiftServiceCaCertificateProvider struct{} + +func (p OpenshiftServiceCaCertificateProvider) InjectCABundle(obj client.Object, cfg render.CertificateProvisionerConfig) error { + switch obj.(type) { + case *admissionregistrationv1.ValidatingWebhookConfiguration: + p.addInjectCABundleAnnotation(obj) + case *admissionregistrationv1.MutatingWebhookConfiguration: + p.addInjectCABundleAnnotation(obj) + case *apiextensionsv1.CustomResourceDefinition: + p.addInjectCABundleAnnotation(obj) + case *corev1.Service: + p.addServingCertSecretNameAnnotation(obj, cfg.CertName) + } + return nil +} + +func (p OpenshiftServiceCaCertificateProvider) AdditionalObjects(_ render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return nil, nil +} + +func (p OpenshiftServiceCaCertificateProvider) GetCertSecretInfo(cfg render.CertificateProvisionerConfig) render.CertSecretInfo { + return render.CertSecretInfo{ + SecretName: cfg.CertName, + PrivateKeyKey: "tls.key", + CertificateKey: "tls.crt", + } +} + +func (p OpenshiftServiceCaCertificateProvider) addServingCertSecretNameAnnotation(obj client.Object, certName string) { + injectionAnnotation := map[string]string{ + openshiftServiceCAServingCertNameAnnotation: certName, + } + obj.SetAnnotations(util.MergeMaps(obj.GetAnnotations(), injectionAnnotation)) +} + +func (p OpenshiftServiceCaCertificateProvider) addInjectCABundleAnnotation(obj client.Object) { + injectionAnnotation := map[string]string{ + openshiftServiceCAInjectCABundleAnnotation: "true", + } + obj.SetAnnotations(util.MergeMaps(obj.GetAnnotations(), injectionAnnotation)) +} diff --git a/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca_test.go b/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca_test.go new file mode 100644 index 0000000000..c4ca3e525c --- /dev/null +++ b/internal/operator-controller/rukpak/render/certproviders/openshift_serviceca_test.go @@ -0,0 +1,130 @@ +package certproviders_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/certproviders" +) + +func Test_OpenshiftServiceCAProvider_InjectCABundle(t *testing.T) { + for _, tc := range []struct { + name string + obj client.Object + cfg render.CertificateProvisionerConfig + expectedObj client.Object + }{ + { + name: "injects inject-cabundle annotation in validating webhook configuration", + obj: &admissionregistrationv1.ValidatingWebhookConfiguration{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.openshift.io/inject-cabundle": "true", + }, + }, + }, + }, + { + name: "injects inject-cabundle annotation in mutating webhook configuration", + obj: &admissionregistrationv1.MutatingWebhookConfiguration{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.openshift.io/inject-cabundle": "true", + }, + }, + }, + }, + { + name: "injects inject-cabundle annotation in custom resource definition", + obj: &apiextensionsv1.CustomResourceDefinition{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.openshift.io/inject-cabundle": "true", + }, + }, + }, + }, + { + name: "injects serving-cert-secret-name annotation in service resource referencing the certificate name", + obj: &corev1.Service{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.openshift.io/serving-cert-secret-name": "cert-name", + }, + }, + }, + }, + { + name: "ignores other objects", + obj: &corev1.Secret{}, + cfg: render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }, + expectedObj: &corev1.Secret{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + certProvider := certproviders.OpenshiftServiceCaCertificateProvider{} + require.NoError(t, certProvider.InjectCABundle(tc.obj, tc.cfg)) + require.Equal(t, tc.expectedObj, tc.obj) + }) + } +} + +func Test_OpenshiftServiceCAProvider_AdditionalObjects(t *testing.T) { + certProvider := certproviders.OpenshiftServiceCaCertificateProvider{} + objs, err := certProvider.AdditionalObjects(render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }) + require.NoError(t, err) + require.Nil(t, objs) +} + +func Test_OpenshiftServiceCAProvider_GetCertSecretInfo(t *testing.T) { + certProvider := certproviders.OpenshiftServiceCaCertificateProvider{} + certInfo := certProvider.GetCertSecretInfo(render.CertificateProvisionerConfig{ + ServiceName: "webhook-service", + Namespace: "namespace", + CertName: "cert-name", + }) + require.Equal(t, render.CertSecretInfo{ + SecretName: "cert-name", + PrivateKeyKey: "tls.key", + CertificateKey: "tls.crt", + }, certInfo) +} diff --git a/internal/operator-controller/rukpak/render/fake.go b/internal/operator-controller/rukpak/render/fake.go new file mode 100644 index 0000000000..c8213d78a8 --- /dev/null +++ b/internal/operator-controller/rukpak/render/fake.go @@ -0,0 +1,24 @@ +package render + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type FakeCertProvider struct { + InjectCABundleFn func(obj client.Object, cfg CertificateProvisionerConfig) error + AdditionalObjectsFn func(cfg CertificateProvisionerConfig) ([]unstructured.Unstructured, error) + GetCertSecretInfoFn func(cfg CertificateProvisionerConfig) CertSecretInfo +} + +func (f FakeCertProvider) InjectCABundle(obj client.Object, cfg CertificateProvisionerConfig) error { + return f.InjectCABundleFn(obj, cfg) +} + +func (f FakeCertProvider) AdditionalObjects(cfg CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return f.AdditionalObjectsFn(cfg) +} + +func (f FakeCertProvider) GetCertSecretInfo(cfg CertificateProvisionerConfig) CertSecretInfo { + return f.GetCertSecretInfoFn(cfg) +} diff --git a/internal/operator-controller/rukpak/render/registryv1/generators/generators.go b/internal/operator-controller/rukpak/render/registryv1/generators/generators.go new file mode 100644 index 0000000000..7d5d435ead --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/generators/generators.go @@ -0,0 +1,580 @@ +package generators + +import ( + "cmp" + "fmt" + "slices" + "strconv" + "strings" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +const ( + labelKubernetesNamespaceMetadataName = "kubernetes.io/metadata.name" +) + +type certVolumeConfig struct { + Name string + Path string + TLSCertPath string + TLSKeyPath string +} + +// certVolumeConfigs contain the expected configurations for certificate volume/mounts +// that the generated Deployment resources for bundle containing webhooks and/or apiservices +// should contain. +var certVolumeConfigs = []certVolumeConfig{ + { + Name: "webhook-cert", + Path: "/tmp/k8s-webhook-server/serving-certs", + TLSCertPath: "tls.crt", + TLSKeyPath: "tls.key", + }, { + Name: "apiservice-cert", + Path: "/apiserver.local.config/certificates", + TLSCertPath: "apiserver.crt", + TLSKeyPath: "apiserver.key", + }, +} + +// BundleCSVDeploymentGenerator generates all deployments defined in rv1's cluster service version (CSV). The generated +// resource aim to have parity with OLMv0 generated Deployment resources: +// - olm.targetNamespaces annotation is set with the opts.TargetNamespace value +// - the deployment spec's revision history limit is set to 1 +// - merges csv annotations to the deployment template's annotations +func BundleCSVDeploymentGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + // collect deployments that service webhooks + webhookDeployments := sets.Set[string]{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + webhookDeployments.Insert(wh.DeploymentName) + } + + objs := make([]client.Object, 0, len(rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs)) + for _, depSpec := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + // Add CSV annotations to template annotations + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/deployment.go#L142 + annotations := util.MergeMaps(rv1.CSV.Annotations, depSpec.Spec.Template.Annotations) + + // In OLMv0 CSVs are annotated with the OperatorGroup's .spec.targetNamespaces + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/operators/olm/operatorgroup.go#L279 + // When the CSVs annotations are copied to the deployment template's annotations, they bring with it this annotation + annotations["olm.targetNamespaces"] = strings.Join(opts.TargetNamespaces, ",") + depSpec.Spec.Template.Annotations = annotations + + // Hardcode the deployment with RevisionHistoryLimit=1 to maintain parity with OLMv0 behaviour. + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/deployment.go#L177-L180 + depSpec.Spec.RevisionHistoryLimit = ptr.To(int32(1)) + + deploymentResource := CreateDeploymentResource( + depSpec.Name, + opts.InstallNamespace, + WithDeploymentSpec(depSpec.Spec), + WithLabels(depSpec.Label), + ) + + secretInfo := render.CertProvisionerFor(depSpec.Name, opts).GetCertSecretInfo() + if webhookDeployments.Has(depSpec.Name) && secretInfo != nil { + ensureCorrectDeploymentCertVolumes(deploymentResource, *secretInfo) + } + + objs = append(objs, deploymentResource) + } + return objs, nil +} + +// BundleCSVPermissionsGenerator generates the Roles and RoleBindings based on bundle's cluster service version +// permission spec. If the bundle is being installed in AllNamespaces mode (opts.TargetNamespaces = [”]) +// no resources will be generated as these permissions will be promoted to ClusterRole/Bunding(s) +func BundleCSVPermissionsGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + // If we're in AllNamespaces mode permissions will be treated as clusterPermissions + if len(opts.TargetNamespaces) == 1 && opts.TargetNamespaces[0] == "" { + return nil, nil + } + + permissions := rv1.CSV.Spec.InstallStrategy.StrategySpec.Permissions + + objs := make([]client.Object, 0, 2*len(opts.TargetNamespaces)*len(permissions)) + for _, ns := range opts.TargetNamespaces { + for _, permission := range permissions { + saName := saNameOrDefault(permission.ServiceAccountName) + name := opts.UniqueNameGenerator(fmt.Sprintf("%s-%s", rv1.CSV.Name, saName), permission) + + objs = append(objs, + CreateRoleResource(name, ns, WithRules(permission.Rules...)), + CreateRoleBindingResource( + name, + ns, + WithSubjects(rbacv1.Subject{Kind: "ServiceAccount", Namespace: opts.InstallNamespace, Name: saName}), + WithRoleRef(rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: name}), + ), + ) + } + } + return objs, nil +} + +// BundleCSVClusterPermissionsGenerator generates ClusterRoles and ClusterRoleBindings based on the bundle's +// cluster service version clusterPermission spec. If the bundle is being installed in AllNamespaces mode +// (opts.TargetNamespaces = [”]), the CSV's permission spec will be promoted to ClusterRole and ClusterRoleBinding +// resources. To keep parity with OLMv0, these will also include an extra rule to get, list, watch namespaces +// (see https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/operators/olm/operatorgroup.go#L539) +func BundleCSVClusterPermissionsGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + clusterPermissions := rv1.CSV.Spec.InstallStrategy.StrategySpec.ClusterPermissions + + // If we're in AllNamespaces mode, promote the permissions to clusterPermissions + if len(opts.TargetNamespaces) == 1 && opts.TargetNamespaces[0] == "" { + for _, p := range rv1.CSV.Spec.InstallStrategy.StrategySpec.Permissions { + p.Rules = append(p.Rules, rbacv1.PolicyRule{ + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{corev1.GroupName}, + Resources: []string{"namespaces"}, + }) + clusterPermissions = append(clusterPermissions, p) + } + } + + objs := make([]client.Object, 0, 2*len(clusterPermissions)) + for _, permission := range clusterPermissions { + saName := saNameOrDefault(permission.ServiceAccountName) + name := opts.UniqueNameGenerator(fmt.Sprintf("%s-%s", rv1.CSV.Name, saName), permission) + objs = append(objs, + CreateClusterRoleResource(name, WithRules(permission.Rules...)), + CreateClusterRoleBindingResource( + name, + WithSubjects(rbacv1.Subject{Kind: "ServiceAccount", Namespace: opts.InstallNamespace, Name: saName}), + WithRoleRef(rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: name}), + ), + ) + } + return objs, nil +} + +// BundleCSVServiceAccountGenerator generates ServiceAccount resources based on the bundle's cluster service version +// permission and clusterPermission spec. One ServiceAccount resource is created / referenced service account (i.e. +// if multiple permissions reference the same service account, only one resource will be generated). +// If a clusterPermission, or permission, references an empty (”) service account, this is considered to be the +// namespace 'default' service account. A resource for the namespace 'default' service account is not generated. +func BundleCSVServiceAccountGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + allPermissions := append( + rv1.CSV.Spec.InstallStrategy.StrategySpec.Permissions, + rv1.CSV.Spec.InstallStrategy.StrategySpec.ClusterPermissions..., + ) + + serviceAccountNames := sets.Set[string]{} + for _, permission := range allPermissions { + serviceAccountNames.Insert(saNameOrDefault(permission.ServiceAccountName)) + } + + objs := make([]client.Object, 0, len(serviceAccountNames)) + for _, serviceAccountName := range serviceAccountNames.UnsortedList() { + // no need to generate the default service account + if serviceAccountName != "default" { + objs = append(objs, CreateServiceAccountResource(serviceAccountName, opts.InstallNamespace)) + } + } + return objs, nil +} + +// BundleCRDGenerator generates CustomResourceDefinition resources from the registry+v1 bundle. If the CRD is referenced +// by any conversion webhook defined in the bundle's cluster service version spec, the CRD is modified +// by the CertificateProvider in opts to add any annotations or modifications necessary for certificate injection. +func BundleCRDGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + // collect deployments to crds with conversion webhooks + crdToDeploymentMap := map[string]v1alpha1.WebhookDescription{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type != v1alpha1.ConversionWebhook { + continue + } + for _, crdName := range wh.ConversionCRDs { + if _, ok := crdToDeploymentMap[crdName]; ok { + return nil, fmt.Errorf("custom resource definition '%s' is referenced by multiple conversion webhook definitions", crdName) + } + crdToDeploymentMap[crdName] = wh + } + } + + objs := make([]client.Object, 0, len(rv1.CRDs)) + for _, crd := range rv1.CRDs { + cp := crd.DeepCopy() + if cw, ok := crdToDeploymentMap[crd.Name]; ok { + // OLMv0 behaviour parity + // See https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/webhook.go#L232 + if crd.Spec.PreserveUnknownFields { + return nil, fmt.Errorf("custom resource definition '%s' must have .spec.preserveUnknownFields set to false to let API Server call webhook to do the conversion", crd.Name) + } + + // OLMv0 behaviour parity + // https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/webhook.go#L242 + conversionWebhookPath := "/" + if cw.WebhookPath != nil { + conversionWebhookPath = *cw.WebhookPath + } + + certProvisioner := render.CertProvisionerFor(cw.DeploymentName, opts) + cp.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{ + Strategy: apiextensionsv1.WebhookConverter, + Webhook: &apiextensionsv1.WebhookConversion{ + ClientConfig: &apiextensionsv1.WebhookClientConfig{ + Service: &apiextensionsv1.ServiceReference{ + Namespace: opts.InstallNamespace, + Name: certProvisioner.ServiceName, + Path: &conversionWebhookPath, + Port: &cw.ContainerPort, + }, + }, + ConversionReviewVersions: cw.AdmissionReviewVersions, + }, + } + + if err := certProvisioner.InjectCABundle(cp); err != nil { + return nil, err + } + } + objs = append(objs, cp) + } + return objs, nil +} + +// BundleAdditionalResourcesGenerator generates resources for the additional resources included in the +// bundle. If the bundle resource is namespace scoped, its namespace will be set to the value of opts.InstallNamespace. +func BundleAdditionalResourcesGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + objs := make([]client.Object, 0, len(rv1.Others)) + for _, res := range rv1.Others { + supported, namespaced := registrybundle.IsSupported(res.GetKind()) + if !supported { + return nil, fmt.Errorf("bundle contains unsupported resource: Name: %v, Kind: %v", res.GetName(), res.GetKind()) + } + + obj := res.DeepCopy() + if namespaced { + obj.SetNamespace(opts.InstallNamespace) + } + + objs = append(objs, obj) + } + return objs, nil +} + +// BundleValidatingWebhookResourceGenerator generates ValidatingAdmissionWebhookConfiguration resources based on +// the bundle's cluster service version spec. The resource is modified by the CertificateProvider in opts +// to add any annotations or modifications necessary for certificate injection. +func BundleValidatingWebhookResourceGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + //nolint:prealloc + var objs []client.Object + + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type != v1alpha1.ValidatingAdmissionWebhook { + continue + } + certProvisioner := render.CertProvisionerFor(wh.DeploymentName, opts) + webhookName := strings.TrimSuffix(wh.GenerateName, "-") + webhookResource := CreateValidatingWebhookConfigurationResource( + webhookName, + opts.InstallNamespace, + WithValidatingWebhooks( + admissionregistrationv1.ValidatingWebhook{ + Name: webhookName, + Rules: wh.Rules, + FailurePolicy: wh.FailurePolicy, + MatchPolicy: wh.MatchPolicy, + ObjectSelector: wh.ObjectSelector, + SideEffects: wh.SideEffects, + TimeoutSeconds: wh.TimeoutSeconds, + AdmissionReviewVersions: wh.AdmissionReviewVersions, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: opts.InstallNamespace, + Name: certProvisioner.ServiceName, + Path: wh.WebhookPath, + Port: &wh.ContainerPort, + }, + }, + // It is safe to create a namespace selector even for cluster scoped CRs. A webhook + // is never skipped for cluster scoped CRs. + NamespaceSelector: getWebhookNamespaceSelector(opts.TargetNamespaces), + }, + ), + ) + if err := certProvisioner.InjectCABundle(webhookResource); err != nil { + return nil, err + } + objs = append(objs, webhookResource) + } + return objs, nil +} + +// BundleMutatingWebhookResourceGenerator generates MutatingAdmissionWebhookConfiguration resources based on +// the bundle's cluster service version spec. The resource is modified by the CertificateProvider in opts +// to add any annotations or modifications necessary for certificate injection. +func BundleMutatingWebhookResourceGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + //nolint:prealloc + var objs []client.Object + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type != v1alpha1.MutatingAdmissionWebhook { + continue + } + certProvisioner := render.CertProvisionerFor(wh.DeploymentName, opts) + webhookName := strings.TrimSuffix(wh.GenerateName, "-") + webhookResource := CreateMutatingWebhookConfigurationResource( + webhookName, + opts.InstallNamespace, + WithMutatingWebhooks( + admissionregistrationv1.MutatingWebhook{ + Name: webhookName, + Rules: wh.Rules, + FailurePolicy: wh.FailurePolicy, + MatchPolicy: wh.MatchPolicy, + ObjectSelector: wh.ObjectSelector, + SideEffects: wh.SideEffects, + TimeoutSeconds: wh.TimeoutSeconds, + AdmissionReviewVersions: wh.AdmissionReviewVersions, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: opts.InstallNamespace, + Name: certProvisioner.ServiceName, + Path: wh.WebhookPath, + Port: &wh.ContainerPort, + }, + }, + ReinvocationPolicy: wh.ReinvocationPolicy, + // It is safe to create a namespace selector even for cluster scoped CRs. A webhook + // is never skipped for cluster scoped CRs. + NamespaceSelector: getWebhookNamespaceSelector(opts.TargetNamespaces), + }, + ), + ) + if err := certProvisioner.InjectCABundle(webhookResource); err != nil { + return nil, err + } + objs = append(objs, webhookResource) + } + return objs, nil +} + +// BundleDeploymentServiceResourceGenerator generates Service resources that support, e.g. the webhooks, +// defined in the bundle's cluster service version spec. The resource is modified by the CertificateProvider in opts +// to add any annotations or modifications necessary for certificate injection. +func BundleDeploymentServiceResourceGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + if rv1 == nil { + return nil, fmt.Errorf("bundle cannot be nil") + } + + // collect webhook service ports + webhookServicePortsByDeployment := map[string]sets.Set[corev1.ServicePort]{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if _, ok := webhookServicePortsByDeployment[wh.DeploymentName]; !ok { + webhookServicePortsByDeployment[wh.DeploymentName] = sets.Set[corev1.ServicePort]{} + } + webhookServicePortsByDeployment[wh.DeploymentName].Insert(getWebhookServicePort(wh)) + } + + objs := make([]client.Object, 0, len(webhookServicePortsByDeployment)) + for _, deploymentSpec := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + if _, ok := webhookServicePortsByDeployment[deploymentSpec.Name]; !ok { + continue + } + + servicePorts := webhookServicePortsByDeployment[deploymentSpec.Name] + ports := servicePorts.UnsortedList() + slices.SortStableFunc(ports, func(a, b corev1.ServicePort) int { + return cmp.Or(cmp.Compare(a.Port, b.Port), cmp.Compare(a.TargetPort.IntValue(), b.TargetPort.IntValue())) + }) + + var labelSelector map[string]string + if deploymentSpec.Spec.Selector != nil { + labelSelector = deploymentSpec.Spec.Selector.MatchLabels + } + + certProvisioner := render.CertProvisionerFor(deploymentSpec.Name, opts) + serviceResource := CreateServiceResource( + certProvisioner.ServiceName, + opts.InstallNamespace, + WithServiceSpec( + corev1.ServiceSpec{ + Ports: ports, + Selector: labelSelector, + }, + ), + ) + + if err := certProvisioner.InjectCABundle(serviceResource); err != nil { + return nil, err + } + objs = append(objs, serviceResource) + } + + return objs, nil +} + +// CertProviderResourceGenerator generates any resources necessary for the CertificateProvider +// in opts to function correctly, e.g. Issuer or Certificate resources. +func CertProviderResourceGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + deploymentsWithWebhooks := sets.Set[string]{} + + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + deploymentsWithWebhooks.Insert(wh.DeploymentName) + } + + var objs []client.Object + for _, depName := range deploymentsWithWebhooks.UnsortedList() { + certCfg := render.CertProvisionerFor(depName, opts) + certObjs, err := certCfg.AdditionalObjects() + if err != nil { + return nil, err + } + for _, certObj := range certObjs { + objs = append(objs, &certObj) + } + } + return objs, nil +} + +func saNameOrDefault(saName string) string { + return cmp.Or(saName, "default") +} + +func getWebhookServicePort(wh v1alpha1.WebhookDescription) corev1.ServicePort { + containerPort := int32(443) + if wh.ContainerPort > 0 { + containerPort = wh.ContainerPort + } + + targetPort := intstr.FromInt32(containerPort) + if wh.TargetPort != nil { + targetPort = *wh.TargetPort + } + + return corev1.ServicePort{ + Name: strconv.Itoa(int(containerPort)), + Port: containerPort, + TargetPort: targetPort, + } +} + +// ensureCorrectDeploymentCertVolumes ensures the deployment has the correct certificate volume mounts by +// - removing all existing volumes with protected certificate volume names (i.e. webhook-cert and apiservice-cert) +// - removing all existing volumes that point to the protected certificate paths (e.g. /tmp/k8s-webhook-server/serving-certs) +// - adding the correct certificate volumes with the correct configuration +// - applying the same changes to all container volume mounts +func ensureCorrectDeploymentCertVolumes(dep *appsv1.Deployment, certSecretInfo render.CertSecretInfo) { + // collect volumes and paths to replace + volumesToRemove := sets.New[string]() + protectedVolumePaths := sets.New[string]() + certVolumes := make([]corev1.Volume, 0, len(certVolumeConfigs)) + certVolumeMounts := make([]corev1.VolumeMount, 0, len(certVolumeConfigs)) + for _, cfg := range certVolumeConfigs { + volumesToRemove.Insert(cfg.Name) + protectedVolumePaths.Insert(cfg.Path) + certVolumes = append(certVolumes, corev1.Volume{ + Name: cfg.Name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: certSecretInfo.SecretName, + Items: []corev1.KeyToPath{ + { + Key: certSecretInfo.CertificateKey, + Path: cfg.TLSCertPath, + }, + { + Key: certSecretInfo.PrivateKeyKey, + Path: cfg.TLSKeyPath, + }, + }, + }, + }, + }) + certVolumeMounts = append(certVolumeMounts, corev1.VolumeMount{ + Name: cfg.Name, + MountPath: cfg.Path, + }) + } + + for _, c := range dep.Spec.Template.Spec.Containers { + for _, containerVolumeMount := range c.VolumeMounts { + if protectedVolumePaths.Has(containerVolumeMount.MountPath) { + volumesToRemove.Insert(containerVolumeMount.Name) + } + } + } + + // update pod volumes + dep.Spec.Template.Spec.Volumes = slices.Concat( + slices.DeleteFunc(dep.Spec.Template.Spec.Volumes, func(v corev1.Volume) bool { + return volumesToRemove.Has(v.Name) + }), + certVolumes, + ) + + // update container volume mounts + for i := range dep.Spec.Template.Spec.Containers { + dep.Spec.Template.Spec.Containers[i].VolumeMounts = slices.Concat( + slices.DeleteFunc(dep.Spec.Template.Spec.Containers[i].VolumeMounts, func(v corev1.VolumeMount) bool { + return volumesToRemove.Has(v.Name) + }), + certVolumeMounts, + ) + } +} + +// getWebhookNamespaceSelector returns a label selector that matches any namespace in targetNamespaces. +// If targetNamespaces is empty, nil, or includes "" (signifying all namespaces) nil is returned. +func getWebhookNamespaceSelector(targetNamespaces []string) *metav1.LabelSelector { + if len(targetNamespaces) > 0 && !slices.Contains(targetNamespaces, "") { + return &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: labelKubernetesNamespaceMetadataName, + Operator: metav1.LabelSelectorOpIn, + Values: targetNamespaces, + }, + }, + } + } + return nil +} diff --git a/internal/operator-controller/rukpak/render/registryv1/generators/generators_test.go b/internal/operator-controller/rukpak/render/registryv1/generators/generators_test.go new file mode 100644 index 0000000000..59be3c6df1 --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/generators/generators_test.go @@ -0,0 +1,2510 @@ +package generators_test + +import ( + "cmp" + "fmt" + "slices" + "testing" + + "github.com/stretchr/testify/require" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/generators" + . "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_ResourceGenerators(t *testing.T) { + g := render.ResourceGenerators{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&corev1.Service{}}, nil + }, + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&corev1.ConfigMap{}}, nil + }, + } + + objs, err := g.GenerateResources(&bundle.RegistryV1{}, render.Options{}) + require.NoError(t, err) + require.Equal(t, []client.Object{&corev1.Service{}, &corev1.ConfigMap{}}, objs) +} + +func Test_ResourceGenerators_Errors(t *testing.T) { + g := render.ResourceGenerators{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&corev1.Service{}}, nil + }, + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return nil, fmt.Errorf("generator error") + }, + } + + objs, err := g.GenerateResources(&bundle.RegistryV1{}, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "generator error") +} + +func Test_BundleCSVDeploymentGenerator_Succeeds(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + opts render.Options + expectedResources []client.Object + }{ + { + name: "generates deployment resources", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithAnnotations(map[string]string{ + "csv": "annotation", + }). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "deployment-one", + Label: map[string]string{ + "bar": "foo", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "pod": "annotation", + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: "some-service-account", + }, + }, + }, + }, + v1alpha1.StrategyDeploymentSpec{ + Name: "deployment-two", + Spec: appsv1.DeploymentSpec{}, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "install-namespace", + Name: "deployment-one", + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: appsv1.DeploymentSpec{ + RevisionHistoryLimit: ptr.To(int32(1)), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "csv": "annotation", + "olm.targetNamespaces": "watch-namespace-one,watch-namespace-two", + "pod": "annotation", + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: "some-service-account", + }, + }, + }, + }, + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "install-namespace", + Name: "deployment-two", + }, + Spec: appsv1.DeploymentSpec{ + RevisionHistoryLimit: ptr.To(int32(1)), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "csv": "annotation", + "olm.targetNamespaces": "watch-namespace-one,watch-namespace-two", + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleCSVDeploymentGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + require.Equal(t, tc.expectedResources, objs) + }) + } +} + +func Test_BundleCSVDeploymentGenerator_WithCertWithCertProvider_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + GetCertSecretInfoFn: func(cfg render.CertificateProvisionerConfig) render.CertSecretInfo { + return render.CertSecretInfo{ + SecretName: "some-secret", + CertificateKey: "some-cert-key", + PrivateKeyKey: "some-private-key-key", + } + }, + } + + b := &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + DeploymentName: "deployment-one", + }). + // deployment must have a referencing webhook (or owned apiservice) definition to trigger cert secret + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "deployment-one", + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + // volume that have neither protected names: webhook-cert and apiservice-cert, + // or target protected certificate paths should remain untouched + { + Name: "some-other-mount", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + // volume mounts with protected names will be rewritten to ensure they point to + // the right certificate path. If they do not exist, they will be created. + { + Name: "webhook-cert", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + // volumes that point to protected paths will be removed + { + Name: "some-mount", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "some-webhook-cert-mount", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "container-1", + VolumeMounts: []corev1.VolumeMount{ + // the mount path for the following volume will be replaced + // since the volume name is protected + { + Name: "webhook-cert", + MountPath: "/webhook-cert-path", + }, + // the following volume will be preserved + { + Name: "some-other-mount", + MountPath: "/some/other/mount/path", + }, + // these volume mount will be removed for referencing protected cert paths + { + Name: "some-webhook-cert-mount", + MountPath: "/tmp/k8s-webhook-server/serving-certs", + }, { + Name: "some-mount", + MountPath: "/apiserver.local.config/certificates", + }, + }, + }, + { + Name: "container-2", + // expect cert volumes to be injected + }, + }, + }, + }, + }, + }, + ).Build(), + } + + objs, err := generators.BundleCSVDeploymentGenerator(b, render.Options{ + InstallNamespace: "install-namespace", + CertificateProvider: fakeProvider, + }) + require.NoError(t, err) + require.Len(t, objs, 1) + + deployment := objs[0].(*appsv1.Deployment) + require.NotNil(t, deployment) + + require.Equal(t, []corev1.Volume{ + { + Name: "some-other-mount", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "webhook-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "some-secret", + Items: []corev1.KeyToPath{ + { + Key: "some-cert-key", + Path: "tls.crt", + }, + { + Key: "some-private-key-key", + Path: "tls.key", + }, + }, + }, + }, + }, + { + Name: "apiservice-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "some-secret", + Items: []corev1.KeyToPath{ + { + Key: "some-cert-key", + Path: "apiserver.crt", + }, + { + Key: "some-private-key-key", + Path: "apiserver.key", + }, + }, + }, + }, + }, + }, deployment.Spec.Template.Spec.Volumes) + require.Equal(t, []corev1.Container{ + { + Name: "container-1", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "some-other-mount", + MountPath: "/some/other/mount/path", + }, + { + Name: "webhook-cert", + MountPath: "/tmp/k8s-webhook-server/serving-certs", + }, + { + Name: "apiservice-cert", + MountPath: "/apiserver.local.config/certificates", + }, + }, + }, + { + Name: "container-2", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "webhook-cert", + MountPath: "/tmp/k8s-webhook-server/serving-certs", + }, + { + Name: "apiservice-cert", + MountPath: "/apiserver.local.config/certificates", + }, + }, + }, + }, deployment.Spec.Template.Spec.Containers) +} + +func Test_BundleCSVDeploymentGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleCSVDeploymentGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleCSVPermissionsGenerator_Succeeds(t *testing.T) { + fakeUniqueNameGenerator := func(base string, _ interface{}) string { + return base + } + + for _, tc := range []struct { + name string + opts render.Options + bundle *bundle.RegistryV1 + expectedResources []client.Object + }{ + { + name: "does not generate any resources when in AllNamespaces mode (target namespace is [''])", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + ).Build(), + }, + expectedResources: nil, + }, + { + name: "generates role and rolebinding for permission service-account when in Single/OwnNamespace mode (target namespace contains a single namespace)", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-service-account-one", + }, + }, + }, + }, + { + name: "generates role and rolebinding for permission service-account for each target namespace when in MultiNamespace install mode (target namespace contains multiple namespaces)", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace", "watch-namespace-two"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-service-account-one", + }, + }, + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace-two", + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace-two", + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-service-account-one", + }, + }, + }, + }, + { + name: "generates role and rolebinding for each permission service-account", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-two", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-service-account-one", + }, + }, + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-two", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-service-account-two", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-two", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-service-account-two", + }, + }, + }, + }, + { + name: "treats empty service account as 'default' service account", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-default", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "watch-namespace", + Name: "csv-default", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "default", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "csv-default", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleCSVPermissionsGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + for i := range objs { + require.Equal(t, tc.expectedResources[i], objs[i], "failed to find expected resource at index %d", i) + } + require.Len(t, objs, len(tc.expectedResources)) + }) + } +} + +func Test_BundleCSVPermissionGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleCSVPermissionsGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleCSVClusterPermissionsGenerator_Succeeds(t *testing.T) { + fakeUniqueNameGenerator := func(base string, _ interface{}) string { + return base + } + + for _, tc := range []struct { + name string + opts render.Options + bundle *bundle.RegistryV1 + expectedResources []client.Object + }{ + { + name: "promotes permissions to clusters permissions and adds namespace policy rule when in AllNamespaces mode (target namespace is [''])", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-two", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{corev1.GroupName}, + Resources: []string{"namespaces"}, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "csv-service-account-one", + }, + }, + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-two", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{corev1.GroupName}, + Resources: []string{"namespaces"}, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-two", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-two", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "csv-service-account-two", + }, + }, + }, + }, + { + name: "generates clusterroles and clusterrolebindings for clusterpermissions", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithClusterPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-one", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-two", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-one", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-one", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-one", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "csv-service-account-one", + }, + }, + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-two", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-service-account-two", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "service-account-two", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "csv-service-account-two", + }, + }, + }, + }, + { + name: "treats empty service accounts as 'default' service account", + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace"}, + UniqueNameGenerator: fakeUniqueNameGenerator, + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithClusterPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-default", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "csv-default", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: "default", + Namespace: "install-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "csv-default", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleCSVClusterPermissionsGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + for i := range objs { + require.Equal(t, tc.expectedResources[i], objs[i], "failed to find expected resource at index %d", i) + } + require.Len(t, objs, len(tc.expectedResources)) + }) + } +} + +func Test_BundleCSVClusterPermissionGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleCSVClusterPermissionsGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleCSVServiceAccountGenerator_Succeeds(t *testing.T) { + for _, tc := range []struct { + name string + opts render.Options + bundle *bundle.RegistryV1 + expectedResources []client.Object + }{ + { + name: "generates unique set of clusterpermissions and permissions service accounts in the install namespace", + opts: render.Options{ + InstallNamespace: "install-namespace", + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-1", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-2", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ). + WithClusterPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-2", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account-3", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments"}, + Verbs: []string{"create"}, + }, + }, + }, + ).Build(), + }, + expectedResources: []client.Object{ + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "service-account-1", + Namespace: "install-namespace", + }, + }, + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "service-account-2", + Namespace: "install-namespace", + }, + }, + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "service-account-3", + Namespace: "install-namespace", + }, + }, + }, + }, + { + name: "treats empty service accounts as default and doesn't generate them", + opts: render.Options{ + InstallNamespace: "install-namespace", + }, + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("csv"). + WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + ). + WithClusterPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + ).Build(), + }, + expectedResources: nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleCSVServiceAccountGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + slices.SortFunc(objs, func(a, b client.Object) int { + return cmp.Compare(a.GetName(), b.GetName()) + }) + for i := range objs { + require.Equal(t, tc.expectedResources[i], objs[i], "failed to find expected resource at index %d", i) + } + require.Len(t, objs, len(tc.expectedResources)) + }) + } +} + +func Test_BundleCSVServiceAccountGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleCSVServiceAccountGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleCRDGenerator_Succeeds(t *testing.T) { + opts := render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + } + + bundle := &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "crd-one"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "crd-two"}}, + }, + } + + objs, err := generators.BundleCRDGenerator(bundle, opts) + require.NoError(t, err) + require.Equal(t, []client.Object{ + &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: "crd-one"}}, + &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: "crd-two"}}, + }, objs) +} + +func Test_BundleCRDGenerator_WithConversionWebhook_Succeeds(t *testing.T) { + opts := render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + } + + bundle := &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "crd-one"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "crd-two"}}, + }, + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + WebhookPath: ptr.To("/some/path"), + ContainerPort: 8443, + AdmissionReviewVersions: []string{"v1", "v1beta1"}, + ConversionCRDs: []string{"crd-one"}, + DeploymentName: "some-deployment", + }, + v1alpha1.WebhookDescription{ + // should use / as WebhookPath by default + Type: v1alpha1.ConversionWebhook, + ContainerPort: 8443, + AdmissionReviewVersions: []string{"v1", "v1beta1"}, + ConversionCRDs: []string{"crd-two"}, + DeploymentName: "some-deployment", + }, + ).Build(), + } + + objs, err := generators.BundleCRDGenerator(bundle, opts) + require.NoError(t, err) + require.Equal(t, []client.Object{ + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "crd-one", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Conversion: &apiextensionsv1.CustomResourceConversion{ + Strategy: apiextensionsv1.WebhookConverter, + Webhook: &apiextensionsv1.WebhookConversion{ + ClientConfig: &apiextensionsv1.WebhookClientConfig{ + Service: &apiextensionsv1.ServiceReference{ + Namespace: "install-namespace", + Name: "some-deployment-service", + Path: ptr.To("/some/path"), + Port: ptr.To(int32(8443)), + }, + }, + ConversionReviewVersions: []string{"v1", "v1beta1"}, + }, + }, + }, + }, + &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "crd-two", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Conversion: &apiextensionsv1.CustomResourceConversion{ + Strategy: apiextensionsv1.WebhookConverter, + Webhook: &apiextensionsv1.WebhookConversion{ + ClientConfig: &apiextensionsv1.WebhookClientConfig{ + Service: &apiextensionsv1.ServiceReference{ + Namespace: "install-namespace", + Name: "some-deployment-service", + Path: ptr.To("/"), + Port: ptr.To(int32(8443)), + }, + }, + ConversionReviewVersions: []string{"v1", "v1beta1"}, + }, + }, + }, + }, + }, objs) +} + +func Test_BundleCRDGenerator_WithConversionWebhook_Fails(t *testing.T) { + opts := render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + } + + bundle := &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + { + ObjectMeta: metav1.ObjectMeta{Name: "crd-one"}, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + PreserveUnknownFields: true, + }, + }, + }, + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + WebhookPath: ptr.To("/some/path"), + ContainerPort: 8443, + AdmissionReviewVersions: []string{"v1", "v1beta1"}, + ConversionCRDs: []string{"crd-one"}, + DeploymentName: "some-deployment", + }, + ).Build(), + } + + objs, err := generators.BundleCRDGenerator(bundle, opts) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "must have .spec.preserveUnknownFields set to false to let API Server call webhook to do the conversion") +} + +func Test_BundleCRDGenerator_WithCertProvider_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + obj.SetAnnotations(map[string]string{ + "cert-provider": "annotation", + }) + return nil + }, + } + + opts := render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + CertificateProvider: fakeProvider, + } + + bundle := &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "crd-one"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "crd-two"}}, + }, + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + ConversionCRDs: []string{ + "crd-one", + }, + }, + ).Build(), + } + + objs, err := generators.BundleCRDGenerator(bundle, opts) + require.NoError(t, err) + require.Len(t, objs, 2) + require.Equal(t, map[string]string{ + "cert-provider": "annotation", + }, objs[0].GetAnnotations()) +} + +func Test_BundleCRDGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleCRDGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleAdditionalResourcesGenerator_Succeeds(t *testing.T) { + opts := render.Options{ + InstallNamespace: "install-namespace", + } + + bundle := &bundle.RegistryV1{ + Others: []unstructured.Unstructured{ + *ToUnstructuredT(t, + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bundled-service", + }, + }, + ), + *ToUnstructuredT(t, + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bundled-clusterrole", + }, + }, + ), + }, + } + + objs, err := generators.BundleAdditionalResourcesGenerator(bundle, opts) + require.NoError(t, err) + require.Len(t, objs, 2) +} + +func Test_BundleAdditionalResourcesGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleAdditionalResourcesGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleValidatingWebhookResourceGenerator_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + obj.SetAnnotations(map[string]string{ + "cert-provider": "annotation", + }) + return nil + }, + } + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + opts render.Options + expectedResources []client.Object + }{ + { + name: "generates validating webhook configuration resources described in the bundle's cluster service version", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "my-webhook", + DeploymentName: "my-deployment", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + WebhookPath: ptr.To("/webhook-path"), + ContainerPort: 443, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.ValidatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "ValidatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + }, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + { + Name: "my-webhook", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Path: ptr.To("/webhook-path"), + Port: ptr.To(int32(443)), + }, + }, + // No NamespaceSelector is set targetNamespaces = []string{""} (AllNamespaces install mode) + }, + }, + }, + }, + }, + { + name: "removes any - suffixes from the webhook name (v0 used GenerateName to allow multiple operator installations - we don't want that in v1)", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "my-webhook-", + DeploymentName: "my-deployment", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + WebhookPath: ptr.To("/webhook-path"), + ContainerPort: 443, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.ValidatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "ValidatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + }, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + { + Name: "my-webhook", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Path: ptr.To("/webhook-path"), + Port: ptr.To(int32(443)), + }, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates validating webhook configuration resources with certificate provider modifications", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "my-webhook", + DeploymentName: "my-deployment", + ContainerPort: 443, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + CertificateProvider: fakeProvider, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.ValidatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "ValidatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + Annotations: map[string]string{ + "cert-provider": "annotation", + }, + }, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + { + Name: "my-webhook", + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Port: ptr.To(int32(443)), + }, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleValidatingWebhookResourceGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + require.Equal(t, tc.expectedResources, objs) + }) + } +} + +func Test_BundleValidatingWebhookResourceGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleValidatingWebhookResourceGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleMutatingWebhookResourceGenerator_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + obj.SetAnnotations(map[string]string{ + "cert-provider": "annotation", + }) + return nil + }, + } + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + opts render.Options + expectedResources []client.Object + }{ + { + name: "generates validating webhook configuration resources described in the bundle's cluster service version", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "my-webhook", + DeploymentName: "my-deployment", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + WebhookPath: ptr.To("/webhook-path"), + ContainerPort: 443, + ReinvocationPolicy: ptr.To(admissionregistrationv1.IfNeededReinvocationPolicy), + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{""}, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "my-webhook", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + ReinvocationPolicy: ptr.To(admissionregistrationv1.IfNeededReinvocationPolicy), + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Path: ptr.To("/webhook-path"), + Port: ptr.To(int32(443)), + }, + }, + // No NamespaceSelector is set targetNamespaces = []string{""} (AllNamespaces install mode) + }, + }, + }, + }, + }, + { + name: "removes any - suffixes from the webhook name (v0 used GenerateName to allow multiple operator installations - we don't want that in v1)", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "my-webhook-", + DeploymentName: "my-deployment", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + WebhookPath: ptr.To("/webhook-path"), + ContainerPort: 443, + ReinvocationPolicy: ptr.To(admissionregistrationv1.IfNeededReinvocationPolicy), + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "my-webhook", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.OperationAll, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + }, + FailurePolicy: ptr.To(admissionregistrationv1.Fail), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: ptr.To(int32(1)), + AdmissionReviewVersions: []string{ + "v1beta1", + "v1beta2", + }, + ReinvocationPolicy: ptr.To(admissionregistrationv1.IfNeededReinvocationPolicy), + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Path: ptr.To("/webhook-path"), + Port: ptr.To(int32(443)), + }, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates validating webhook configuration resources with certificate provider modifications", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "my-webhook", + DeploymentName: "my-deployment", + ContainerPort: 443, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + CertificateProvider: fakeProvider, + }, + expectedResources: []client.Object{ + &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-webhook", + Namespace: "install-namespace", + Annotations: map[string]string{ + "cert-provider": "annotation", + }, + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "my-webhook", + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: "install-namespace", + Name: "my-deployment-service", + Port: ptr.To(int32(443)), + }, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "kubernetes.io/metadata.name", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleMutatingWebhookResourceGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + require.Equal(t, tc.expectedResources, objs) + }) + } +} + +func Test_BundleMutatingWebhookResourceGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleMutatingWebhookResourceGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_BundleDeploymentServiceResourceGenerator_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + InjectCABundleFn: func(obj client.Object, cfg render.CertificateProvisionerConfig) error { + obj.SetAnnotations(map[string]string{ + "cert-provider": "annotation", + }) + return nil + }, + } + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + opts render.Options + expectedResources []client.Object + }{ + { + name: "generates webhook services using container port 443 and target port 443 by default", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + DeploymentName: "my-deployment", + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "443", + Port: int32(443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 443, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates webhook services using the given container port and setting target port the same as the container port if not given", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + DeploymentName: "my-deployment", + ContainerPort: int32(8443), + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "8443", + Port: int32(8443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates webhook services using given container port of 443 and given target port", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + TargetPort: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8080, + }, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "443", + Port: int32(443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8080, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates webhook services using given container port and target port", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + ContainerPort: int32(9090), + TargetPort: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "9090", + Port: int32(9090), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + }, + }, + }, + }, + }, + { + name: "generates webhook services using referenced deployment defined label selector", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + }, + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + ContainerPort: int32(9090), + TargetPort: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "9090", + Port: int32(9090), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + }, + Selector: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + { + name: "aggregates all webhook definitions referencing the same deployment into a single service", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }, + }, + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + DeploymentName: "my-deployment", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + DeploymentName: "my-deployment", + ContainerPort: int32(8443), + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + TargetPort: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8080, + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + DeploymentName: "my-deployment", + ContainerPort: int32(9090), + TargetPort: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "443", + Port: int32(443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 443, + }, + }, { + Name: "443", + Port: int32(443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8080, + }, + }, { + Name: "8443", + Port: int32(8443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, + }, + }, { + Name: "9090", + Port: int32(9090), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9099, + }, + }, + }, + Selector: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + { + name: "applies cert provider modifiers to webhook service", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + DeploymentName: "my-deployment", + }, + ).Build(), + }, + opts: render.Options{ + InstallNamespace: "install-namespace", + TargetNamespaces: []string{"watch-namespace-one", "watch-namespace-two"}, + CertificateProvider: fakeProvider, + }, + expectedResources: []client.Object{ + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-deployment-service", + Namespace: "install-namespace", + Annotations: map[string]string{ + "cert-provider": "annotation", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "443", + Port: int32(443), + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 443, + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + objs, err := generators.BundleDeploymentServiceResourceGenerator(tc.bundle, tc.opts) + require.NoError(t, err) + require.Equal(t, tc.expectedResources, objs) + }) + } +} + +func Test_BundleDeploymentServiceResourceGenerator_FailsOnNil(t *testing.T) { + objs, err := generators.BundleMutatingWebhookResourceGenerator(nil, render.Options{}) + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "bundle cannot be nil") +} + +func Test_CertProviderResourceGenerator_Succeeds(t *testing.T) { + fakeProvider := FakeCertProvider{ + AdditionalObjectsFn: func(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return []unstructured.Unstructured{*ToUnstructuredT(t, &corev1.Secret{ + TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Name: cfg.CertName, + }, + })}, nil + }, + } + + objs, err := generators.CertProviderResourceGenerator(&bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + // only generate resources for deployments referenced by webhook definitions + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + DeploymentName: "my-deployment", + }, + ). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "my-deployment", + }, + v1alpha1.StrategyDeploymentSpec{ + Name: "my-other-deployment", + }, + ).Build(), + }, render.Options{ + InstallNamespace: "install-namespace", + CertificateProvider: fakeProvider, + }) + require.NoError(t, err) + require.Equal(t, []client.Object{ + ToUnstructuredT(t, &corev1.Secret{ + TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{Name: "my-deployment-service-cert"}, + }), + }, objs) +} diff --git a/internal/operator-controller/rukpak/render/registryv1/generators/resources.go b/internal/operator-controller/rukpak/render/registryv1/generators/resources.go new file mode 100644 index 0000000000..ed1cf6552f --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/generators/resources.go @@ -0,0 +1,264 @@ +package generators + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ResourceCreatorOption = func(client.Object) +type ResourceCreatorOptions []ResourceCreatorOption + +func (r ResourceCreatorOptions) ApplyTo(obj client.Object) client.Object { + if obj == nil { + return nil + } + for _, opt := range r { + if opt != nil { + opt(obj) + } + } + return obj +} + +// WithSubjects applies rbac subjects to ClusterRoleBinding and RoleBinding resources +func WithSubjects(subjects ...rbacv1.Subject) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *rbacv1.RoleBinding: + o.Subjects = subjects + case *rbacv1.ClusterRoleBinding: + o.Subjects = subjects + default: + panic("unknown object type") + } + } +} + +// WithRoleRef applies rbac RoleRef to ClusterRoleBinding and RoleBinding resources +func WithRoleRef(roleRef rbacv1.RoleRef) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *rbacv1.RoleBinding: + o.RoleRef = roleRef + case *rbacv1.ClusterRoleBinding: + o.RoleRef = roleRef + default: + panic("unknown object type") + } + } +} + +// WithRules applies rbac PolicyRules to Role and ClusterRole resources +func WithRules(rules ...rbacv1.PolicyRule) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *rbacv1.Role: + o.Rules = rules + case *rbacv1.ClusterRole: + o.Rules = rules + default: + panic("unknown object type") + } + } +} + +// WithDeploymentSpec applies a DeploymentSpec to Deployment resources +func WithDeploymentSpec(depSpec appsv1.DeploymentSpec) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *appsv1.Deployment: + o.Spec = depSpec + default: + panic("unknown object type") + } + } +} + +// WithLabels applies labels to the metadata of any resource +func WithLabels(labels map[string]string) func(client.Object) { + return func(obj client.Object) { + obj.SetLabels(labels) + } +} + +// WithServiceSpec applies a service spec to a Service resource +func WithServiceSpec(serviceSpec corev1.ServiceSpec) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *corev1.Service: + o.Spec = serviceSpec + } + } +} + +// WithValidatingWebhooks applies validating webhooks to a ValidatingWebhookConfiguration resource +func WithValidatingWebhooks(webhooks ...admissionregistrationv1.ValidatingWebhook) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *admissionregistrationv1.ValidatingWebhookConfiguration: + o.Webhooks = webhooks + } + } +} + +// WithMutatingWebhooks applies mutating webhooks to a MutatingWebhookConfiguration resource +func WithMutatingWebhooks(webhooks ...admissionregistrationv1.MutatingWebhook) func(client.Object) { + return func(obj client.Object) { + switch o := obj.(type) { + case *admissionregistrationv1.MutatingWebhookConfiguration: + o.Webhooks = webhooks + } + } +} + +// CreateServiceAccountResource creates a ServiceAccount resource with name 'name', namespace 'namespace', and applying +// any ServiceAccount related options in opts +func CreateServiceAccountResource(name string, namespace string, opts ...ResourceCreatorOption) *corev1.ServiceAccount { + return ResourceCreatorOptions(opts).ApplyTo( + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, + ).(*corev1.ServiceAccount) +} + +// CreateRoleResource creates a Role resource with name 'name' and namespace 'namespace' and applying any +// Role related options in opts +func CreateRoleResource(name string, namespace string, opts ...ResourceCreatorOption) *rbacv1.Role { + return ResourceCreatorOptions(opts).ApplyTo( + &rbacv1.Role{ + TypeMeta: metav1.TypeMeta{ + Kind: "Role", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, + ).(*rbacv1.Role) +} + +// CreateClusterRoleResource creates a ClusterRole resource with name 'name' and applying any +// ClusterRole related options in opts +func CreateClusterRoleResource(name string, opts ...ResourceCreatorOption) *rbacv1.ClusterRole { + return ResourceCreatorOptions(opts).ApplyTo( + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, + ).(*rbacv1.ClusterRole) +} + +// CreateClusterRoleBindingResource creates a ClusterRoleBinding resource with name 'name' and applying any +// ClusterRoleBinding related options in opts +func CreateClusterRoleBindingResource(name string, opts ...ResourceCreatorOption) *rbacv1.ClusterRoleBinding { + return ResourceCreatorOptions(opts).ApplyTo( + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, + ).(*rbacv1.ClusterRoleBinding) +} + +// CreateRoleBindingResource creates a RoleBinding resource with name 'name', namespace 'namespace', and applying any +// RoleBinding related options in opts +func CreateRoleBindingResource(name string, namespace string, opts ...ResourceCreatorOption) *rbacv1.RoleBinding { + return ResourceCreatorOptions(opts).ApplyTo( + &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{ + Kind: "RoleBinding", + APIVersion: rbacv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, + ).(*rbacv1.RoleBinding) +} + +// CreateDeploymentResource creates a Deployment resource with name 'name', namespace 'namespace', and applying any +// Deployment related options in opts +func CreateDeploymentResource(name string, namespace string, opts ...ResourceCreatorOption) *appsv1.Deployment { + return ResourceCreatorOptions(opts).ApplyTo( + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, + ).(*appsv1.Deployment) +} + +// CreateValidatingWebhookConfigurationResource creates a ValidatingWebhookConfiguration resource with name 'name', +// namespace 'namespace', and applying any ValidatingWebhookConfiguration related options in opts +func CreateValidatingWebhookConfigurationResource(name string, namespace string, opts ...ResourceCreatorOption) *admissionregistrationv1.ValidatingWebhookConfiguration { + return ResourceCreatorOptions(opts).ApplyTo( + &admissionregistrationv1.ValidatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "ValidatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + ).(*admissionregistrationv1.ValidatingWebhookConfiguration) +} + +// CreateMutatingWebhookConfigurationResource creates a MutatingWebhookConfiguration resource with name 'name', +// namespace 'namespace', and applying any MutatingWebhookConfiguration related options in opts +func CreateMutatingWebhookConfigurationResource(name string, namespace string, opts ...ResourceCreatorOption) *admissionregistrationv1.MutatingWebhookConfiguration { + return ResourceCreatorOptions(opts).ApplyTo( + &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: admissionregistrationv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + ).(*admissionregistrationv1.MutatingWebhookConfiguration) +} + +// CreateServiceResource creates a Service resource with name 'name', namespace 'namespace', and applying any Service related options in opts +func CreateServiceResource(name string, namespace string, opts ...ResourceCreatorOption) *corev1.Service { + return ResourceCreatorOptions(opts).ApplyTo(&corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }).(*corev1.Service) +} diff --git a/internal/operator-controller/rukpak/render/registryv1/generators/resources_test.go b/internal/operator-controller/rukpak/render/registryv1/generators/resources_test.go new file mode 100644 index 0000000000..aa3227987e --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/generators/resources_test.go @@ -0,0 +1,278 @@ +package generators_test + +import ( + "maps" + "slices" + "strings" + "testing" + + "github.com/stretchr/testify/require" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/generators" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +func Test_OptionsApplyToExecutesIgnoresNil(t *testing.T) { + opts := []generators.ResourceCreatorOption{ + func(object client.Object) { + object.SetAnnotations(util.MergeMaps(object.GetAnnotations(), map[string]string{"h": ""})) + }, + nil, + func(object client.Object) { + object.SetAnnotations(util.MergeMaps(object.GetAnnotations(), map[string]string{"i": ""})) + }, + nil, + } + + require.Nil(t, generators.ResourceCreatorOptions(nil).ApplyTo(nil)) + require.Nil(t, generators.ResourceCreatorOptions([]generators.ResourceCreatorOption{}).ApplyTo(nil)) + + obj := generators.ResourceCreatorOptions(opts).ApplyTo(&corev1.ConfigMap{}) + require.Equal(t, "hi", strings.Join(slices.Sorted(maps.Keys(obj.GetAnnotations())), "")) +} + +func Test_CreateServiceAccount(t *testing.T) { + svc := generators.CreateServiceAccountResource("my-sa", "my-namespace") + require.NotNil(t, svc) + require.Equal(t, "my-sa", svc.Name) + require.Equal(t, "my-namespace", svc.Namespace) +} + +func Test_CreateRole(t *testing.T) { + role := generators.CreateRoleResource("my-role", "my-namespace") + require.NotNil(t, role) + require.Equal(t, "my-role", role.Name) + require.Equal(t, "my-namespace", role.Namespace) +} + +func Test_CreateRoleBinding(t *testing.T) { + roleBinding := generators.CreateRoleBindingResource("my-role-binding", "my-namespace") + require.NotNil(t, roleBinding) + require.Equal(t, "my-role-binding", roleBinding.Name) + require.Equal(t, "my-namespace", roleBinding.Namespace) +} + +func Test_CreateClusterRole(t *testing.T) { + clusterRole := generators.CreateClusterRoleResource("my-cluster-role") + require.NotNil(t, clusterRole) + require.Equal(t, "my-cluster-role", clusterRole.Name) +} + +func Test_CreateClusterRoleBinding(t *testing.T) { + clusterRoleBinding := generators.CreateClusterRoleBindingResource("my-cluster-role-binding") + require.NotNil(t, clusterRoleBinding) + require.Equal(t, "my-cluster-role-binding", clusterRoleBinding.Name) +} + +func Test_CreateDeployment(t *testing.T) { + deployment := generators.CreateDeploymentResource("my-deployment", "my-namespace") + require.NotNil(t, deployment) + require.Equal(t, "my-deployment", deployment.Name) + require.Equal(t, "my-namespace", deployment.Namespace) +} + +func Test_CreateService(t *testing.T) { + svc := generators.CreateServiceResource("my-service", "my-namespace") + require.NotNil(t, svc) + require.Equal(t, "my-service", svc.Name) + require.Equal(t, "my-namespace", svc.Namespace) +} + +func Test_CreateValidatingWebhookConfiguration(t *testing.T) { + wh := generators.CreateValidatingWebhookConfigurationResource("my-validating-webhook-configuration", "my-namespace") + require.NotNil(t, wh) + require.Equal(t, "my-validating-webhook-configuration", wh.Name) + require.Equal(t, "my-namespace", wh.Namespace) +} + +func Test_CreateMutatingWebhookConfiguration(t *testing.T) { + wh := generators.CreateMutatingWebhookConfigurationResource("my-mutating-webhook-configuration", "my-namespace") + require.NotNil(t, wh) + require.Equal(t, "my-mutating-webhook-configuration", wh.Name) + require.Equal(t, "my-namespace", wh.Namespace) +} + +func Test_WithSubjects(t *testing.T) { + for _, tc := range []struct { + name string + subjects []rbacv1.Subject + }{ + { + name: "empty", + subjects: []rbacv1.Subject{}, + }, { + name: "nil", + subjects: nil, + }, { + name: "single subject", + subjects: []rbacv1.Subject{ + { + APIGroup: rbacv1.GroupName, + Kind: rbacv1.ServiceAccountKind, + Name: "my-sa", + Namespace: "my-namespace", + }, + }, + }, { + name: "multiple subjects", + subjects: []rbacv1.Subject{ + { + APIGroup: rbacv1.GroupName, + Kind: rbacv1.ServiceAccountKind, + Name: "my-sa", + Namespace: "my-namespace", + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + roleBinding := generators.CreateRoleBindingResource("my-role", "my-namespace", generators.WithSubjects(tc.subjects...)) + require.NotNil(t, roleBinding) + require.Equal(t, roleBinding.Subjects, tc.subjects) + + clusterRoleBinding := generators.CreateClusterRoleBindingResource("my-role", generators.WithSubjects(tc.subjects...)) + require.NotNil(t, clusterRoleBinding) + require.Equal(t, clusterRoleBinding.Subjects, tc.subjects) + }) + } +} + +func Test_WithRules(t *testing.T) { + for _, tc := range []struct { + name string + rules []rbacv1.PolicyRule + }{ + { + name: "empty", + rules: []rbacv1.PolicyRule{}, + }, { + name: "nil", + rules: nil, + }, { + name: "single subject", + rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, { + name: "multiple subjects", + rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + ResourceNames: []string{"my-resource"}, + }, { + Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments", "replicasets", "statefulsets"}, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + role := generators.CreateRoleResource("my-role", "my-namespace", generators.WithRules(tc.rules...)) + require.NotNil(t, role) + require.Equal(t, role.Rules, tc.rules) + + clusterRole := generators.CreateClusterRoleResource("my-role", generators.WithRules(tc.rules...)) + require.NotNil(t, clusterRole) + require.Equal(t, clusterRole.Rules, tc.rules) + }) + } +} + +func Test_WithRoleRef(t *testing.T) { + roleRef := rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: "my-role", + } + + roleBinding := generators.CreateRoleBindingResource("my-role-binding", "my-namespace", generators.WithRoleRef(roleRef)) + require.NotNil(t, roleBinding) + require.Equal(t, roleRef, roleBinding.RoleRef) + + clusterRoleBinding := generators.CreateClusterRoleBindingResource("my-cluster-role-binding", generators.WithRoleRef(roleRef)) + require.NotNil(t, clusterRoleBinding) + require.Equal(t, roleRef, clusterRoleBinding.RoleRef) +} + +func Test_WithLabels(t *testing.T) { + for _, tc := range []struct { + name string + labels map[string]string + }{ + { + name: "empty", + labels: map[string]string{}, + }, { + name: "nil", + labels: nil, + }, { + name: "not empty", + labels: map[string]string{ + "foo": "bar", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + dep := generators.CreateDeploymentResource("my-deployment", "my-namespace", generators.WithLabels(tc.labels)) + require.NotNil(t, dep) + require.Equal(t, tc.labels, dep.Labels) + }) + } +} + +func Test_WithServiceSpec(t *testing.T) { + svc := generators.CreateServiceResource("mysvc", "myns", generators.WithServiceSpec(corev1.ServiceSpec{ + ClusterIP: "1.2.3.4", + })) + require.NotNil(t, svc) + require.Equal(t, corev1.ServiceSpec{ + ClusterIP: "1.2.3.4", + }, svc.Spec) +} + +func Test_WithValidatingWebhook(t *testing.T) { + wh := generators.CreateValidatingWebhookConfigurationResource("mywh", "myns", + generators.WithValidatingWebhooks( + admissionregistrationv1.ValidatingWebhook{ + Name: "wh-one", + }, + admissionregistrationv1.ValidatingWebhook{ + Name: "wh-two", + }, + ), + ) + require.NotNil(t, wh) + require.Equal(t, []admissionregistrationv1.ValidatingWebhook{ + {Name: "wh-one"}, + {Name: "wh-two"}, + }, wh.Webhooks) +} + +func Test_WithMutatingWebhook(t *testing.T) { + wh := generators.CreateMutatingWebhookConfigurationResource("mywh", "myns", + generators.WithMutatingWebhooks( + admissionregistrationv1.MutatingWebhook{ + Name: "wh-one", + }, + admissionregistrationv1.MutatingWebhook{ + Name: "wh-two", + }, + ), + ) + require.NotNil(t, wh) + require.Equal(t, []admissionregistrationv1.MutatingWebhook{ + {Name: "wh-one"}, + {Name: "wh-two"}, + }, wh.Webhooks) +} diff --git a/internal/operator-controller/rukpak/render/registryv1/registryv1.go b/internal/operator-controller/rukpak/render/registryv1/registryv1.go new file mode 100644 index 0000000000..1cfefbb8be --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/registryv1.go @@ -0,0 +1,50 @@ +package registryv1 + +import ( + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/generators" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/validators" +) + +// Renderer renders registry+v1 bundles into plain kubernetes manifests +var Renderer = render.BundleRenderer{ + BundleValidator: BundleValidator, + ResourceGenerators: ResourceGenerators, +} + +// BundleValidator validates RegistryV1 bundles +var BundleValidator = render.BundleValidator{ + // NOTE: if you update this list, Test_BundleValidatorHasAllValidationFns will fail until + // you bring the same changes over to that test. This helps ensure all validation rules are executed + // while giving us the flexibility to test each validation function individually + validators.CheckDeploymentSpecUniqueness, + validators.CheckDeploymentNameIsDNS1123SubDomain, + validators.CheckCRDResourceUniqueness, + validators.CheckOwnedCRDExistence, + validators.CheckPackageNameNotEmpty, + validators.CheckConversionWebhookSupport, + validators.CheckWebhookDeploymentReferentialIntegrity, + validators.CheckWebhookNameUniqueness, + validators.CheckWebhookNameIsDNS1123SubDomain, + validators.CheckConversionWebhookCRDReferenceUniqueness, + validators.CheckConversionWebhooksReferenceOwnedCRDs, + validators.CheckWebhookRules, +} + +// ResourceGenerators a slice of ResourceGenerators required to generate plain resource manifests for +// registry+v1 bundles +var ResourceGenerators = []render.ResourceGenerator{ + // NOTE: if you update this list, Test_ResourceGeneratorsHasAllGenerators will fail until + // you bring the same changes over to that test. This helps ensure all validation rules are executed + // while giving us the flexibility to test each generator individually + generators.BundleCSVServiceAccountGenerator, + generators.BundleCSVPermissionsGenerator, + generators.BundleCSVClusterPermissionsGenerator, + generators.BundleCRDGenerator, + generators.BundleAdditionalResourcesGenerator, + generators.BundleCSVDeploymentGenerator, + generators.BundleValidatingWebhookResourceGenerator, + generators.BundleMutatingWebhookResourceGenerator, + generators.BundleDeploymentServiceResourceGenerator, + generators.CertProviderResourceGenerator, +} diff --git a/internal/operator-controller/rukpak/render/registryv1/registryv1_test.go b/internal/operator-controller/rukpak/render/registryv1/registryv1_test.go new file mode 100644 index 0000000000..b092cc8e11 --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/registryv1_test.go @@ -0,0 +1,124 @@ +package registryv1_test + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/generators" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/validators" + . "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_BundleValidatorHasAllValidationFns(t *testing.T) { + expectedValidationFns := []func(v1 *bundle.RegistryV1) []error{ + validators.CheckDeploymentSpecUniqueness, + validators.CheckDeploymentNameIsDNS1123SubDomain, + validators.CheckCRDResourceUniqueness, + validators.CheckOwnedCRDExistence, + validators.CheckPackageNameNotEmpty, + validators.CheckConversionWebhookSupport, + validators.CheckWebhookDeploymentReferentialIntegrity, + validators.CheckWebhookNameUniqueness, + validators.CheckWebhookNameIsDNS1123SubDomain, + validators.CheckConversionWebhookCRDReferenceUniqueness, + validators.CheckConversionWebhooksReferenceOwnedCRDs, + validators.CheckWebhookRules, + } + actualValidationFns := registryv1.BundleValidator + + require.Len(t, actualValidationFns, len(expectedValidationFns)) + for i := range expectedValidationFns { + require.Equal(t, reflect.ValueOf(expectedValidationFns[i]).Pointer(), reflect.ValueOf(actualValidationFns[i]).Pointer(), "bundle validator has unexpected validation function") + } +} + +func Test_ResourceGeneratorsHasAllGenerators(t *testing.T) { + expectedGenerators := []render.ResourceGenerator{ + generators.BundleCSVServiceAccountGenerator, + generators.BundleCSVPermissionsGenerator, + generators.BundleCSVClusterPermissionsGenerator, + generators.BundleCRDGenerator, + generators.BundleAdditionalResourcesGenerator, + generators.BundleCSVDeploymentGenerator, + generators.BundleValidatingWebhookResourceGenerator, + generators.BundleMutatingWebhookResourceGenerator, + generators.BundleDeploymentServiceResourceGenerator, + generators.CertProviderResourceGenerator, + } + actualGenerators := registryv1.ResourceGenerators + + require.Len(t, actualGenerators, len(expectedGenerators)) + for i := range expectedGenerators { + require.Equal(t, reflect.ValueOf(expectedGenerators[i]).Pointer(), reflect.ValueOf(actualGenerators[i]).Pointer(), "bundle validator has unexpected validation function") + } +} + +func Test_Renderer_Success(t *testing.T) { + someBundle := bundle.RegistryV1{ + PackageName: "my-package", + CSV: clusterserviceversion.Builder(). + WithName("test-bundle"). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + Others: []unstructured.Unstructured{ + *ToUnstructuredT(t, &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service", + }, + }), + }, + } + + objs, err := registryv1.Renderer.Render(someBundle, "install-namespace") + t.Log("Check renderer returns objects and no errors") + require.NoError(t, err) + require.NotEmpty(t, objs) + + t.Log("Check renderer returns a single object") + // bundle only contains a service - bundle csv is empty + require.Len(t, objs, 1) + + t.Log("Check correct name and that the correct namespace was applied") + require.Equal(t, "my-service", objs[0].GetName()) + require.Equal(t, "install-namespace", objs[0].GetNamespace()) +} + +func Test_Renderer_Failure_UnsupportedKind(t *testing.T) { + someBundle := bundle.RegistryV1{ + PackageName: "my-package", + CSV: clusterserviceversion.Builder(). + WithName("test-bundle"). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + Others: []unstructured.Unstructured{ + *ToUnstructuredT(t, &corev1.Event{ + TypeMeta: metav1.TypeMeta{ + Kind: "Event", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "testEvent", + }, + }), + }, + } + + objs, err := registryv1.Renderer.Render(someBundle, "install-namespace") + t.Log("Check renderer returns objects and no errors") + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported resource") + require.Empty(t, objs) +} diff --git a/internal/operator-controller/rukpak/render/registryv1/validators/validator.go b/internal/operator-controller/rukpak/render/registryv1/validators/validator.go new file mode 100644 index 0000000000..60978aa833 --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/validators/validator.go @@ -0,0 +1,329 @@ +package validators + +import ( + "cmp" + "errors" + "fmt" + "maps" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" +) + +// CheckDeploymentSpecUniqueness checks that each strategy deployment spec in the csv has a unique name. +// Errors are sorted by deployment name. +func CheckDeploymentSpecUniqueness(rv1 *bundle.RegistryV1) []error { + deploymentNameSet := sets.Set[string]{} + duplicateDeploymentNames := sets.Set[string]{} + for _, dep := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + if deploymentNameSet.Has(dep.Name) { + duplicateDeploymentNames.Insert(dep.Name) + } + deploymentNameSet.Insert(dep.Name) + } + + errs := make([]error, 0, len(duplicateDeploymentNames)) + for _, d := range slices.Sorted(slices.Values(duplicateDeploymentNames.UnsortedList())) { + errs = append(errs, fmt.Errorf("cluster service version contains duplicate strategy deployment spec '%s'", d)) + } + return errs +} + +// CheckDeploymentNameIsDNS1123SubDomain checks each deployment strategy spec name complies with the Kubernetes +// resource naming conversions +func CheckDeploymentNameIsDNS1123SubDomain(rv1 *bundle.RegistryV1) []error { + deploymentNameErrMap := map[string][]string{} + for _, dep := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + errs := validation.IsDNS1123Subdomain(dep.Name) + if len(errs) > 0 { + slices.Sort(errs) + deploymentNameErrMap[dep.Name] = errs + } + } + + errs := make([]error, 0, len(deploymentNameErrMap)) + for _, dep := range slices.Sorted(maps.Keys(deploymentNameErrMap)) { + errs = append(errs, fmt.Errorf("invalid cluster service version strategy deployment name '%s': %s", dep, strings.Join(deploymentNameErrMap[dep], ", "))) + } + return errs +} + +// CheckOwnedCRDExistence checks bundle owned custom resource definitions declared in the csv exist in the bundle +func CheckOwnedCRDExistence(rv1 *bundle.RegistryV1) []error { + crdsNames := sets.Set[string]{} + for _, crd := range rv1.CRDs { + crdsNames.Insert(crd.Name) + } + + missingCRDNames := sets.Set[string]{} + for _, crd := range rv1.CSV.Spec.CustomResourceDefinitions.Owned { + if !crdsNames.Has(crd.Name) { + missingCRDNames.Insert(crd.Name) + } + } + + errs := make([]error, 0, len(missingCRDNames)) + for _, crdName := range slices.Sorted(slices.Values(missingCRDNames.UnsortedList())) { + errs = append(errs, fmt.Errorf("cluster service definition references owned custom resource definition '%s' not found in bundle", crdName)) + } + return errs +} + +// CheckCRDResourceUniqueness checks that the bundle CRD names are unique +func CheckCRDResourceUniqueness(rv1 *bundle.RegistryV1) []error { + crdsNames := sets.Set[string]{} + duplicateCRDNames := sets.Set[string]{} + for _, crd := range rv1.CRDs { + if crdsNames.Has(crd.Name) { + duplicateCRDNames.Insert(crd.Name) + } + crdsNames.Insert(crd.Name) + } + + errs := make([]error, 0, len(duplicateCRDNames)) + for _, crdName := range slices.Sorted(slices.Values(duplicateCRDNames.UnsortedList())) { + errs = append(errs, fmt.Errorf("bundle contains duplicate custom resource definition '%s'", crdName)) + } + return errs +} + +// CheckPackageNameNotEmpty checks that PackageName is not empty +func CheckPackageNameNotEmpty(rv1 *bundle.RegistryV1) []error { + if rv1.PackageName == "" { + return []error{errors.New("package name is empty")} + } + return nil +} + +// CheckConversionWebhookSupport checks that if the bundle cluster service version declares conversion webhook definitions, +// that the bundle also only supports AllNamespaces install mode. This keeps parity with OLMv0 behavior for conversion webhooks, +// https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/webhook.go#L193 +func CheckConversionWebhookSupport(rv1 *bundle.RegistryV1) []error { + var conversionWebhookNames []string + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type == v1alpha1.ConversionWebhook { + conversionWebhookNames = append(conversionWebhookNames, wh.GenerateName) + } + } + + if len(conversionWebhookNames) > 0 { + supportedInstallModes := sets.Set[v1alpha1.InstallModeType]{} + for _, mode := range rv1.CSV.Spec.InstallModes { + if mode.Supported { + supportedInstallModes.Insert(mode.Type) + } + } + + if len(supportedInstallModes) != 1 || !supportedInstallModes.Has(v1alpha1.InstallModeTypeAllNamespaces) { + sortedModes := slices.Sorted(slices.Values(supportedInstallModes.UnsortedList())) + errs := make([]error, len(conversionWebhookNames)) + for i, webhookName := range conversionWebhookNames { + errs[i] = fmt.Errorf("bundle contains conversion webhook %q and supports install modes %v - conversion webhooks are only supported for bundles that only support AllNamespaces install mode", webhookName, sortedModes) + } + return errs + } + } + + return nil +} + +// CheckWebhookDeploymentReferentialIntegrity checks that each webhook definition in the csv +// references an existing strategy deployment spec. Errors are sorted by strategy deployment spec name, +// webhook type, and webhook name. +func CheckWebhookDeploymentReferentialIntegrity(rv1 *bundle.RegistryV1) []error { + webhooksByDeployment := map[string][]v1alpha1.WebhookDescription{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + webhooksByDeployment[wh.DeploymentName] = append(webhooksByDeployment[wh.DeploymentName], wh) + } + + for _, depl := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { + delete(webhooksByDeployment, depl.Name) + } + + var errs []error + // Loop through sorted keys to keep error messages ordered by deployment name + for _, deploymentName := range slices.Sorted(maps.Keys(webhooksByDeployment)) { + webhookDefns := webhooksByDeployment[deploymentName] + slices.SortFunc(webhookDefns, func(a, b v1alpha1.WebhookDescription) int { + return cmp.Or(cmp.Compare(a.Type, b.Type), cmp.Compare(a.GenerateName, b.GenerateName)) + }) + for _, webhookDef := range webhookDefns { + errs = append(errs, fmt.Errorf("webhook of type '%s' with name '%s' references non-existent deployment '%s'", webhookDef.Type, webhookDef.GenerateName, webhookDef.DeploymentName)) + } + } + return errs +} + +// CheckWebhookNameUniqueness checks that each webhook definition of each type (validating, mutating, or conversion) +// has a unique name. Webhooks of different types can have the same name. Errors are sorted by webhook type +// and name. +func CheckWebhookNameUniqueness(rv1 *bundle.RegistryV1) []error { + webhookNameSetByType := map[v1alpha1.WebhookAdmissionType]sets.Set[string]{} + duplicateWebhooksByType := map[v1alpha1.WebhookAdmissionType]sets.Set[string]{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if _, ok := webhookNameSetByType[wh.Type]; !ok { + webhookNameSetByType[wh.Type] = sets.Set[string]{} + } + if webhookNameSetByType[wh.Type].Has(wh.GenerateName) { + if _, ok := duplicateWebhooksByType[wh.Type]; !ok { + duplicateWebhooksByType[wh.Type] = sets.Set[string]{} + } + duplicateWebhooksByType[wh.Type].Insert(wh.GenerateName) + } + webhookNameSetByType[wh.Type].Insert(wh.GenerateName) + } + + var errs []error + for _, whType := range slices.Sorted(maps.Keys(duplicateWebhooksByType)) { + for _, webhookName := range slices.Sorted(slices.Values(duplicateWebhooksByType[whType].UnsortedList())) { + errs = append(errs, fmt.Errorf("duplicate webhook '%s' of type '%s'", webhookName, whType)) + } + } + return errs +} + +// CheckConversionWebhooksReferenceOwnedCRDs checks defined conversion webhooks reference bundle owned CRDs. +// Errors are sorted by webhook name and CRD name. +func CheckConversionWebhooksReferenceOwnedCRDs(rv1 *bundle.RegistryV1) []error { + //nolint:prealloc + var conversionWebhooks []v1alpha1.WebhookDescription + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type != v1alpha1.ConversionWebhook { + continue + } + conversionWebhooks = append(conversionWebhooks, wh) + } + + if len(conversionWebhooks) == 0 { + return nil + } + + ownedCRDNames := sets.Set[string]{} + for _, crd := range rv1.CSV.Spec.CustomResourceDefinitions.Owned { + ownedCRDNames.Insert(crd.Name) + } + + slices.SortFunc(conversionWebhooks, func(a, b v1alpha1.WebhookDescription) int { + return cmp.Compare(a.GenerateName, b.GenerateName) + }) + + var errs []error + for _, webhook := range conversionWebhooks { + webhookCRDs := webhook.ConversionCRDs + slices.Sort(webhookCRDs) + for _, crd := range webhookCRDs { + if !ownedCRDNames.Has(crd) { + errs = append(errs, fmt.Errorf("conversion webhook '%s' references custom resource definition '%s' not owned bundle", webhook.GenerateName, crd)) + } + } + } + return errs +} + +// CheckConversionWebhookCRDReferenceUniqueness checks no two (or more) conversion webhooks reference the same CRD. +func CheckConversionWebhookCRDReferenceUniqueness(rv1 *bundle.RegistryV1) []error { + // collect webhooks by crd + crdToWh := map[string][]string{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if wh.Type != v1alpha1.ConversionWebhook { + continue + } + for _, crd := range wh.ConversionCRDs { + crdToWh[crd] = append(crdToWh[crd], wh.GenerateName) + } + } + + // remove crds with single webhook + maps.DeleteFunc(crdToWh, func(crd string, whs []string) bool { + return len(whs) == 1 + }) + + errs := make([]error, 0, len(crdToWh)) + orderedCRDs := slices.Sorted(maps.Keys(crdToWh)) + for _, crd := range orderedCRDs { + orderedWhs := strings.Join(slices.Sorted(slices.Values(crdToWh[crd])), ",") + errs = append(errs, fmt.Errorf("conversion webhooks [%s] reference same custom resource definition '%s'", orderedWhs, crd)) + } + return errs +} + +// CheckWebhookNameIsDNS1123SubDomain checks each webhook configuration name complies with the Kubernetes resource naming conversions +func CheckWebhookNameIsDNS1123SubDomain(rv1 *bundle.RegistryV1) []error { + invalidWebhooksByType := map[v1alpha1.WebhookAdmissionType]map[string][]string{} + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + if _, ok := invalidWebhooksByType[wh.Type]; !ok { + invalidWebhooksByType[wh.Type] = map[string][]string{} + } + errs := validation.IsDNS1123Subdomain(wh.GenerateName) + if len(errs) > 0 { + slices.Sort(errs) + invalidWebhooksByType[wh.Type][wh.GenerateName] = errs + } + } + + var errs []error + for _, whType := range slices.Sorted(maps.Keys(invalidWebhooksByType)) { + for _, webhookName := range slices.Sorted(maps.Keys(invalidWebhooksByType[whType])) { + errs = append(errs, fmt.Errorf("webhook of type '%s' has invalid name '%s': %s", whType, webhookName, strings.Join(invalidWebhooksByType[whType][webhookName], ","))) + } + } + return errs +} + +// forbiddenWebhookRuleAPIGroups contain the API groups that are forbidden for webhook configuration rules in OLMv1 +var forbiddenWebhookRuleAPIGroups = sets.New("olm.operatorframework.io", "*") + +// forbiddenAdmissionRegistrationResources contain the resources that are forbidden for webhook configuration rules +// for the admissionregistration.k8s.io api group +var forbiddenAdmissionRegistrationResources = sets.New( + "*", + "mutatingwebhookconfiguration", + "mutatingwebhookconfigurations", + "validatingwebhookconfiguration", + "validatingwebhookconfigurations", +) + +// CheckWebhookRules ensures webhook rules do not reference forbidden API groups or resources in line with OLMv0 behavior +// The following are forbidden, rules targeting: +// - all API groups (i.e. '*') +// - OLMv1 API group (i.e. 'olm.operatorframework.io') +// - all resources under the 'admissionregistration.k8s.io' API group +// - the 'ValidatingWebhookConfiguration' resource under the 'admissionregistration.k8s.io' API group +// - the 'MutatingWebhookConfiguration' resource under the 'admissionregistration.k8s.io' API group +// +// These boundaries attempt to reduce the blast radius of faulty webhooks and avoid deadlocks preventing the user +// from deleting OLMv1 resources installing and managing the faulty webhook, or deleting faulty admission webhook +// configurations. +// See https://github.com/operator-framework/operator-lifecycle-manager/blob/ccf0c4c91f1e7673e87f3a18947f9a1f88d48438/pkg/controller/install/webhook.go#L19 +// for more details +func CheckWebhookRules(rv1 *bundle.RegistryV1) []error { + var errs []error + for _, wh := range rv1.CSV.Spec.WebhookDefinitions { + // Rules are not used for conversion webhooks + if wh.Type == v1alpha1.ConversionWebhook { + continue + } + webhookName := wh.GenerateName + for _, rule := range wh.Rules { + for _, apiGroup := range rule.APIGroups { + if forbiddenWebhookRuleAPIGroups.Has(apiGroup) { + errs = append(errs, fmt.Errorf("webhook %q contains forbidden rule: admission webhook rules cannot reference API group %q", webhookName, apiGroup)) + } + if apiGroup == "admissionregistration.k8s.io" { + for _, resource := range rule.Resources { + if forbiddenAdmissionRegistrationResources.Has(strings.ToLower(resource)) { + errs = append(errs, fmt.Errorf("webhook %q contains forbidden rule: admission webhook rules cannot reference resource %q for API group %q", webhookName, resource, apiGroup)) + } + } + } + } + } + } + return errs +} diff --git a/internal/operator-controller/rukpak/render/registryv1/validators/validator_test.go b/internal/operator-controller/rukpak/render/registryv1/validators/validator_test.go new file mode 100644 index 0000000000..b9377c81a4 --- /dev/null +++ b/internal/operator-controller/rukpak/render/registryv1/validators/validator_test.go @@ -0,0 +1,1173 @@ +package validators_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1/validators" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_CheckDeploymentSpecUniqueness(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with unique deployment strategy spec names", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-two"}, + ).Build(), + }, + expectedErrs: []error{}, + }, { + name: "rejects bundles with duplicate deployment strategy spec names", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-two"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + ).Build(), + }, + expectedErrs: []error{ + errors.New("cluster service version contains duplicate strategy deployment spec 'test-deployment-one'"), + }, + }, { + name: "errors are ordered by deployment strategy spec name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-a"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-b"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-c"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-b"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-a"}, + ).Build(), + }, + expectedErrs: []error{ + errors.New("cluster service version contains duplicate strategy deployment spec 'test-deployment-a'"), + errors.New("cluster service version contains duplicate strategy deployment spec 'test-deployment-b'"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckDeploymentSpecUniqueness(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckDeploymentNameIsDNS1123SubDomain(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts valid deployment strategy spec names", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-two"}, + ).Build(), + }, + expectedErrs: []error{}, + }, { + name: "rejects bundles with invalid deployment strategy spec names - errors are sorted by name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "-bad-name"}, + v1alpha1.StrategyDeploymentSpec{Name: "b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long"}, + v1alpha1.StrategyDeploymentSpec{Name: "a-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-"}, + ).Build(), + }, + expectedErrs: []error{ + errors.New("invalid cluster service version strategy deployment name '-bad-name': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + errors.New("invalid cluster service version strategy deployment name 'a-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'), must be no more than 253 characters"), + errors.New("invalid cluster service version strategy deployment name 'b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long': must be no more than 253 characters"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckDeploymentNameIsDNS1123SubDomain(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CRDResourceUniqueness(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with unique custom resource definition resources", + bundle: &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "b.crd.something"}}, + }, + }, + expectedErrs: []error{}, + }, { + name: "rejects bundles with duplicate custom resource definition resources", + bundle: &bundle.RegistryV1{CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + }}, + expectedErrs: []error{ + errors.New("bundle contains duplicate custom resource definition 'a.crd.something'"), + }, + }, { + name: "errors are ordered by custom resource definition name", + bundle: &bundle.RegistryV1{CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "c.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "c.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + }}, + expectedErrs: []error{ + errors.New("bundle contains duplicate custom resource definition 'a.crd.something'"), + errors.New("bundle contains duplicate custom resource definition 'c.crd.something'"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := validators.CheckCRDResourceUniqueness(tc.bundle) + require.Equal(t, tc.expectedErrs, err) + }) + } +} + +func Test_CheckOwnedCRDExistence(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with existing owned custom resource definition resources", + bundle: &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{ + {ObjectMeta: metav1.ObjectMeta{Name: "a.crd.something"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "b.crd.something"}}, + }, + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "a.crd.something"}, + v1alpha1.CRDDescription{Name: "b.crd.something"}, + ).Build(), + }, + expectedErrs: []error{}, + }, { + name: "rejects bundles with missing owned custom resource definition resources", + bundle: &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{}, + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs(v1alpha1.CRDDescription{Name: "a.crd.something"}).Build(), + }, + expectedErrs: []error{ + errors.New("cluster service definition references owned custom resource definition 'a.crd.something' not found in bundle"), + }, + }, { + name: "errors are ordered by owned custom resource definition name", + bundle: &bundle.RegistryV1{ + CRDs: []apiextensionsv1.CustomResourceDefinition{}, + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "a.crd.something"}, + v1alpha1.CRDDescription{Name: "c.crd.something"}, + v1alpha1.CRDDescription{Name: "b.crd.something"}, + ).Build(), + }, + expectedErrs: []error{ + errors.New("cluster service definition references owned custom resource definition 'a.crd.something' not found in bundle"), + errors.New("cluster service definition references owned custom resource definition 'b.crd.something' not found in bundle"), + errors.New("cluster service definition references owned custom resource definition 'c.crd.something' not found in bundle"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckOwnedCRDExistence(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckPackageNameNotEmpty(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with non-empty package name", + bundle: &bundle.RegistryV1{ + PackageName: "not-empty", + }, + }, { + name: "rejects bundles with empty package name", + bundle: &bundle.RegistryV1{}, + expectedErrs: []error{ + errors.New("package name is empty"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckPackageNameNotEmpty(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckWebhookSupport(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with conversion webhook definitions when they only support AllNamespaces install mode", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + }, + ).Build(), + }, + }, + { + name: "accepts bundles with validating webhook definitions when they support more modes than AllNamespaces install mode", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + }, + ).Build(), + }, + }, + { + name: "accepts bundles with mutating webhook definitions when they support more modes than AllNamespaces install mode", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + }, + ).Build(), + }, + }, + { + name: "rejects bundles with conversion webhook definitions when they support more modes than AllNamespaces install mode", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + GenerateName: "webhook-b", + Type: v1alpha1.ConversionWebhook, + }, + v1alpha1.WebhookDescription{ + GenerateName: "webhook-a", + Type: v1alpha1.ConversionWebhook, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("bundle contains conversion webhook \"webhook-b\" and supports install modes [AllNamespaces SingleNamespace] - conversion webhooks are only supported for bundles that only support AllNamespaces install mode"), + errors.New("bundle contains conversion webhook \"webhook-a\" and supports install modes [AllNamespaces SingleNamespace] - conversion webhooks are only supported for bundles that only support AllNamespaces install mode"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckConversionWebhookSupport(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckWebhookRules(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles with webhook definitions without rules", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + }, + ).Build(), + }, + }, + { + name: "accepts bundles with webhook definitions with supported rules", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"appsv1"}, + Resources: []string{"deployments", "replicasets", "statefulsets"}, + }, + }, + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + Resources: []string{"services"}, + }, + }, + }, + }, + ).Build(), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing '*' api group", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-z", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"*"}, + }, + }, + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"*"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-z\" contains forbidden rule: admission webhook rules cannot reference API group \"*\""), + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference API group \"*\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'olm.operatorframework.io' api group", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-z", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"olm.operatorframework.io"}, + }, + }, + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"olm.operatorframework.io"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-z\" contains forbidden rule: admission webhook rules cannot reference API group \"olm.operatorframework.io\""), + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference API group \"olm.operatorframework.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and '*' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"*"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"*\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'MutatingWebhookConfiguration' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"MutatingWebhookConfiguration"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"MutatingWebhookConfiguration\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'mutatingwebhookconfiguration' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"mutatingwebhookconfiguration"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"mutatingwebhookconfiguration\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'mutatingwebhookconfigurations' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"mutatingwebhookconfigurations"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"mutatingwebhookconfigurations\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'ValidatingWebhookConfiguration' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"ValidatingWebhookConfiguration"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"ValidatingWebhookConfiguration\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'validatingwebhookconfiguration' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"validatingwebhookconfiguration"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"validatingwebhookconfiguration\" for API group \"admissionregistration.k8s.io\""), + }, + }, + { + name: "reject bundles with webhook definitions with rules containing 'admissionregistration.k8s.io' api group and 'validatingwebhookconfigurations' resource", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "webhook-a", + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{"admissionregistration.k8s.io"}, + Resources: []string{"validatingwebhookconfigurations"}, + }, + }, + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook \"webhook-a\" contains forbidden rule: admission webhook rules cannot reference resource \"validatingwebhookconfigurations\" for API group \"admissionregistration.k8s.io\""), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckWebhookRules(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckWebhookDeploymentReferentialIntegrity(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles where webhook definitions reference existing strategy deployment specs", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-two"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook", + DeploymentName: "test-deployment-one", + }, + ).Build(), + }, + }, { + name: "rejects bundles with webhook definitions that reference non-existing strategy deployment specs", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook", + DeploymentName: "test-deployment-two", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook of type 'ValidatingAdmissionWebhook' with name 'test-webhook' references non-existent deployment 'test-deployment-two'"), + }, + }, { + name: "errors are ordered by deployment strategy spec name, webhook type, and webhook name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{Name: "test-deployment-one"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook-c", + DeploymentName: "test-deployment-c", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-a", + DeploymentName: "test-deployment-a", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-b", + DeploymentName: "test-deployment-b", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-c", + DeploymentName: "test-deployment-c", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-c-b", + DeploymentName: "test-deployment-c", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-c-a", + DeploymentName: "test-deployment-c", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook of type 'MutatingAdmissionWebhook' with name 'test-mute-webhook-a' references non-existent deployment 'test-deployment-a'"), + errors.New("webhook of type 'ConversionWebhook' with name 'test-conv-webhook-b' references non-existent deployment 'test-deployment-b'"), + errors.New("webhook of type 'ConversionWebhook' with name 'test-conv-webhook-c-a' references non-existent deployment 'test-deployment-c'"), + errors.New("webhook of type 'ConversionWebhook' with name 'test-conv-webhook-c-b' references non-existent deployment 'test-deployment-c'"), + errors.New("webhook of type 'MutatingAdmissionWebhook' with name 'test-mute-webhook-c' references non-existent deployment 'test-deployment-c'"), + errors.New("webhook of type 'ValidatingAdmissionWebhook' with name 'test-val-webhook-c' references non-existent deployment 'test-deployment-c'"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckWebhookDeploymentReferentialIntegrity(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckWebhookNameUniqueness(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles without webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder().Build(), + }, + }, { + name: "accepts bundles with unique webhook names", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook-one", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook-two", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-three", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook-four", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook-five", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-six", + }, + ).Build(), + }, + }, { + name: "accepts bundles with webhooks with the same name but different types", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + }, + ).Build(), + }, + }, { + name: "rejects bundles with duplicate validating webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-webhook", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("duplicate webhook 'test-webhook' of type 'ValidatingAdmissionWebhook'"), + }, + }, { + name: "rejects bundles with duplicate mutating webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-webhook", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("duplicate webhook 'test-webhook' of type 'MutatingAdmissionWebhook'"), + }, + }, { + name: "rejects bundles with duplicate conversion webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("duplicate webhook 'test-webhook' of type 'ConversionWebhook'"), + }, + }, { + name: "orders errors by webhook type and name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook-b", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook-a", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook-a", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook-b", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-b", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-a", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-a", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-conv-webhook-b", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-b", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-a", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-a", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook-b", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("duplicate webhook 'test-conv-webhook-a' of type 'ConversionWebhook'"), + errors.New("duplicate webhook 'test-conv-webhook-b' of type 'ConversionWebhook'"), + errors.New("duplicate webhook 'test-mute-webhook-a' of type 'MutatingAdmissionWebhook'"), + errors.New("duplicate webhook 'test-mute-webhook-b' of type 'MutatingAdmissionWebhook'"), + errors.New("duplicate webhook 'test-val-webhook-a' of type 'ValidatingAdmissionWebhook'"), + errors.New("duplicate webhook 'test-val-webhook-b' of type 'ValidatingAdmissionWebhook'"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckWebhookNameUniqueness(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckConversionWebhooksReferenceOwnedCRDs(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles without webhook definitions", + bundle: &bundle.RegistryV1{}, + }, { + name: "accepts bundles without conversion webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook", + }, + ).Build(), + }, + }, { + name: "accepts bundles with conversion webhooks that reference owned CRDs", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "some.crd.something"}, + v1alpha1.CRDDescription{Name: "another.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + ConversionCRDs: []string{ + "some.crd.something", + "another.crd.something", + }, + }, + ).Build(), + }, + }, { + name: "rejects bundles with conversion webhooks that reference existing CRDs that are not owned", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "some.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + ConversionCRDs: []string{ + "some.crd.something", + "another.crd.something", + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("conversion webhook 'test-webhook' references custom resource definition 'another.crd.something' not owned bundle"), + }, + }, { + name: "errors are ordered by webhook name and CRD name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "b.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-b", + ConversionCRDs: []string{ + "b.crd.something", + }, + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-a", + ConversionCRDs: []string{ + "c.crd.something", + "a.crd.something", + }, + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-c", + ConversionCRDs: []string{ + "a.crd.something", + "d.crd.something", + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("conversion webhook 'test-webhook-a' references custom resource definition 'a.crd.something' not owned bundle"), + errors.New("conversion webhook 'test-webhook-a' references custom resource definition 'c.crd.something' not owned bundle"), + errors.New("conversion webhook 'test-webhook-c' references custom resource definition 'a.crd.something' not owned bundle"), + errors.New("conversion webhook 'test-webhook-c' references custom resource definition 'd.crd.something' not owned bundle"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckConversionWebhooksReferenceOwnedCRDs(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckConversionWebhookCRDReferenceUniqueness(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles without webhook definitions", + bundle: &bundle.RegistryV1{}, + expectedErrs: []error{}, + }, + { + name: "accepts bundles without conversion webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "test-val-webhook", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "test-mute-webhook", + }, + ).Build(), + }, + expectedErrs: []error{}, + }, + { + name: "accepts bundles with conversion webhooks that reference different CRDs", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "some.crd.something"}, + v1alpha1.CRDDescription{Name: "another.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + ConversionCRDs: []string{ + "some.crd.something", + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-2", + ConversionCRDs: []string{ + "another.crd.something", + }, + }, + ).Build(), + }, + expectedErrs: []error{}, + }, + { + name: "rejects bundles with conversion webhooks that reference the same CRD", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "some.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook", + ConversionCRDs: []string{ + "some.crd.something", + }, + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-two", + ConversionCRDs: []string{ + "some.crd.something", + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("conversion webhooks [test-webhook,test-webhook-two] reference same custom resource definition 'some.crd.something'"), + }, + }, + { + name: "errors are ordered by CRD name and webhook names", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "b.crd.something"}, + ). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-b", + ConversionCRDs: []string{ + "b.crd.something", + "a.crd.something", + }, + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-a", + ConversionCRDs: []string{ + "d.crd.something", + "a.crd.something", + "b.crd.something", + }, + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "test-webhook-c", + ConversionCRDs: []string{ + "b.crd.something", + "d.crd.something", + }, + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("conversion webhooks [test-webhook-a,test-webhook-b] reference same custom resource definition 'a.crd.something'"), + errors.New("conversion webhooks [test-webhook-a,test-webhook-b,test-webhook-c] reference same custom resource definition 'b.crd.something'"), + errors.New("conversion webhooks [test-webhook-a,test-webhook-c] reference same custom resource definition 'd.crd.something'"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckConversionWebhookCRDReferenceUniqueness(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} + +func Test_CheckWebhookNameIsDNS1123SubDomain(t *testing.T) { + for _, tc := range []struct { + name string + bundle *bundle.RegistryV1 + expectedErrs []error + }{ + { + name: "accepts bundles without webhook definitions", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder().Build(), + }, + }, { + name: "rejects bundles with invalid webhook definitions names and orders errors by webhook type and name", + bundle: &bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithWebhookDefinitions( + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "a-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ValidatingAdmissionWebhook, + GenerateName: "-bad-name", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "b-bad-name-", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-", + }, v1alpha1.WebhookDescription{ + Type: v1alpha1.MutatingAdmissionWebhook, + GenerateName: "a-bad-name-", + }, + v1alpha1.WebhookDescription{ + Type: v1alpha1.ConversionWebhook, + GenerateName: "a-bad-name-", + }, + ).Build(), + }, + expectedErrs: []error{ + errors.New("webhook of type 'ConversionWebhook' has invalid name 'a-bad-name-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + errors.New("webhook of type 'ConversionWebhook' has invalid name 'b-bad-name-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + errors.New("webhook of type 'MutatingAdmissionWebhook' has invalid name 'a-bad-name-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + errors.New("webhook of type 'MutatingAdmissionWebhook' has invalid name 'b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'),must be no more than 253 characters"), + errors.New("webhook of type 'ValidatingAdmissionWebhook' has invalid name '-bad-name': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + errors.New("webhook of type 'ValidatingAdmissionWebhook' has invalid name 'a-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long': must be no more than 253 characters"), + errors.New("webhook of type 'ValidatingAdmissionWebhook' has invalid name 'b-name-is-waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay-too-long-and-bad-': a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'),must be no more than 253 characters"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := validators.CheckWebhookNameIsDNS1123SubDomain(tc.bundle) + require.Equal(t, tc.expectedErrs, errs) + }) + } +} diff --git a/internal/operator-controller/rukpak/render/render.go b/internal/operator-controller/rukpak/render/render.go new file mode 100644 index 0000000000..f7e419c783 --- /dev/null +++ b/internal/operator-controller/rukpak/render/render.go @@ -0,0 +1,211 @@ +package render + +import ( + "errors" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" + hashutil "github.com/operator-framework/operator-controller/internal/shared/util/hash" +) + +// BundleValidator validates a RegistryV1 bundle by executing a series of +// checks on it and collecting any errors that were found +type BundleValidator []func(v1 *bundle.RegistryV1) []error + +func (v BundleValidator) Validate(rv1 *bundle.RegistryV1) error { + var errs []error + for _, validator := range v { + errs = append(errs, validator(rv1)...) + } + return errors.Join(errs...) +} + +// ResourceGenerator generates resources given a registry+v1 bundle and options +type ResourceGenerator func(rv1 *bundle.RegistryV1, opts Options) ([]client.Object, error) + +func (g ResourceGenerator) GenerateResources(rv1 *bundle.RegistryV1, opts Options) ([]client.Object, error) { + return g(rv1, opts) +} + +// ResourceGenerators aggregates generators. Its GenerateResource method will call all of its generators and return +// generated resources. +type ResourceGenerators []ResourceGenerator + +func (r ResourceGenerators) GenerateResources(rv1 *bundle.RegistryV1, opts Options) ([]client.Object, error) { + //nolint:prealloc + var renderedObjects []client.Object + for _, generator := range r { + objs, err := generator.GenerateResources(rv1, opts) + if err != nil { + return nil, err + } + renderedObjects = append(renderedObjects, objs...) + } + return renderedObjects, nil +} + +func (r ResourceGenerators) ResourceGenerator() ResourceGenerator { + return r.GenerateResources +} + +type UniqueNameGenerator func(string, interface{}) string + +type Options struct { + InstallNamespace string + TargetNamespaces []string + UniqueNameGenerator UniqueNameGenerator + CertificateProvider CertificateProvider +} + +func (o *Options) apply(opts ...Option) *Options { + for _, opt := range opts { + if opt != nil { + opt(o) + } + } + return o +} + +func (o *Options) validate(rv1 *bundle.RegistryV1) (*Options, []error) { + var errs []error + if o.UniqueNameGenerator == nil { + errs = append(errs, errors.New("unique name generator must be specified")) + } + if err := validateTargetNamespaces(rv1, o.InstallNamespace, o.TargetNamespaces); err != nil { + errs = append(errs, fmt.Errorf("invalid target namespaces %v: %w", o.TargetNamespaces, err)) + } + return o, errs +} + +type Option func(*Options) + +// WithTargetNamespaces sets the target namespaces to be used when rendering the bundle +// The value will only be used if len(namespaces) > 0. Otherwise, the default value for the bundle +// derived from its install mode support will be used (if such a value can be defined). +func WithTargetNamespaces(namespaces ...string) Option { + return func(o *Options) { + if len(namespaces) > 0 { + o.TargetNamespaces = namespaces + } + } +} + +func WithUniqueNameGenerator(generator UniqueNameGenerator) Option { + return func(o *Options) { + o.UniqueNameGenerator = generator + } +} + +func WithCertificateProvider(provider CertificateProvider) Option { + return func(o *Options) { + o.CertificateProvider = provider + } +} + +type BundleRenderer struct { + BundleValidator BundleValidator + ResourceGenerators []ResourceGenerator +} + +func (r BundleRenderer) Render(rv1 bundle.RegistryV1, installNamespace string, opts ...Option) ([]client.Object, error) { + // validate bundle + if err := r.BundleValidator.Validate(&rv1); err != nil { + return nil, err + } + + // generate bundle objects + genOpts, errs := (&Options{ + // default options + InstallNamespace: installNamespace, + TargetNamespaces: defaultTargetNamespacesForBundle(&rv1), + UniqueNameGenerator: DefaultUniqueNameGenerator, + CertificateProvider: nil, + }).apply(opts...).validate(&rv1) + + if len(errs) > 0 { + return nil, fmt.Errorf("invalid option(s): %w", errors.Join(errs...)) + } + + objs, err := ResourceGenerators(r.ResourceGenerators).GenerateResources(&rv1, *genOpts) + if err != nil { + return nil, err + } + + return objs, nil +} + +func DefaultUniqueNameGenerator(base string, o interface{}) string { + hashStr := hashutil.DeepHashObject(o) + return util.ObjectNameForBaseAndSuffix(base, hashStr) +} + +func validateTargetNamespaces(rv1 *bundle.RegistryV1, installNamespace string, targetNamespaces []string) error { + supportedInstallModes := supportedBundleInstallModes(rv1) + + set := sets.New[string](targetNamespaces...) + switch { + case set.Len() == 0: + // Note: this function generally expects targetNamespace to contain at least one value set by default + // in case the user does not specify the value. The option to set the targetNamespace is a no-op if it is empty. + // The only case for which a default targetNamespace is undefined is in the case of a bundle that only + // supports SingleNamespace install mode. The if statement here is added to provide a more friendly error + // message than just the generic (at least one target namespace must be specified) which would occur + // in case only the MultiNamespace install mode is supported by the bundle. + // If AllNamespaces mode is supported, the default will be [""] -> watch all namespaces + // If only OwnNamespace is supported, the default will be [install-namespace] -> only watch the install/own namespace + if supportedInstallModes.Has(v1alpha1.InstallModeTypeMultiNamespace) { + return errors.New("at least one target namespace must be specified") + } + return errors.New("exactly one target namespace must be specified") + case set.Len() == 1 && set.Has(""): + if supportedInstallModes.Has(v1alpha1.InstallModeTypeAllNamespaces) { + return nil + } + return fmt.Errorf("supported install modes %v do not support targeting all namespaces", sets.List(supportedInstallModes)) + case set.Len() == 1 && !set.Has(""): + if targetNamespaces[0] == installNamespace { + if !supportedInstallModes.Has(v1alpha1.InstallModeTypeOwnNamespace) { + return fmt.Errorf("supported install modes %v do not support targeting own namespace", sets.List(supportedInstallModes)) + } + return nil + } + if supportedInstallModes.Has(v1alpha1.InstallModeTypeSingleNamespace) { + return nil + } + default: + if !supportedInstallModes.Has(v1alpha1.InstallModeTypeOwnNamespace) && set.Has(installNamespace) { + return fmt.Errorf("supported install modes %v do not support targeting own namespace", sets.List(supportedInstallModes)) + } + if supportedInstallModes.Has(v1alpha1.InstallModeTypeMultiNamespace) && !set.Has("") { + return nil + } + } + return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[v1alpha1.InstallModeType](supportedInstallModes), targetNamespaces) +} + +func defaultTargetNamespacesForBundle(rv1 *bundle.RegistryV1) []string { + supportedInstallModes := supportedBundleInstallModes(rv1) + + if supportedInstallModes.Has(v1alpha1.InstallModeTypeAllNamespaces) { + return []string{corev1.NamespaceAll} + } + + return nil +} + +func supportedBundleInstallModes(rv1 *bundle.RegistryV1) sets.Set[v1alpha1.InstallModeType] { + supportedInstallModes := sets.New[v1alpha1.InstallModeType]() + for _, im := range rv1.CSV.Spec.InstallModes { + if im.Supported { + supportedInstallModes.Insert(im.Type) + } + } + return supportedInstallModes +} diff --git a/internal/operator-controller/rukpak/render/render_test.go b/internal/operator-controller/rukpak/render/render_test.go new file mode 100644 index 0000000000..ca14598896 --- /dev/null +++ b/internal/operator-controller/rukpak/render/render_test.go @@ -0,0 +1,384 @@ +package render_test + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + . "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_BundleRenderer_NoConfig(t *testing.T) { + renderer := render.BundleRenderer{} + objs, err := renderer.Render( + bundle.RegistryV1{ + CSV: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + }, "", nil) + require.NoError(t, err) + require.Empty(t, objs) +} + +func Test_BundleRenderer_ValidatesBundle(t *testing.T) { + renderer := render.BundleRenderer{ + BundleValidator: render.BundleValidator{ + func(v1 *bundle.RegistryV1) []error { + return []error{errors.New("this bundle is invalid")} + }, + }, + } + objs, err := renderer.Render(bundle.RegistryV1{}, "") + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "this bundle is invalid") +} + +func Test_BundleRenderer_CreatesCorrectDefaultOptions(t *testing.T) { + expectedInstallNamespace := "install-namespace" + expectedTargetNamespaces := []string{""} + expectedUniqueNameGenerator := render.DefaultUniqueNameGenerator + + renderer := render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + require.Equal(t, expectedInstallNamespace, opts.InstallNamespace) + require.Equal(t, expectedTargetNamespaces, opts.TargetNamespaces) + require.Equal(t, reflect.ValueOf(expectedUniqueNameGenerator).Pointer(), reflect.ValueOf(render.DefaultUniqueNameGenerator).Pointer(), "options has unexpected default unique name generator") + return nil, nil + }, + }, + } + + _, _ = renderer.Render(bundle.RegistryV1{}, expectedInstallNamespace) +} + +func Test_BundleRenderer_DefaultTargetNamespaces(t *testing.T) { + for _, tc := range []struct { + name string + supportedInstallModes []v1alpha1.InstallModeType + expectedTargetNamespaces []string + expectedErrMsg string + }{ + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, OwnNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, SingleNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeMultiNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, OwnNamespace, SingleNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeSingleNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, OwnNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, SingleNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "Default to AllNamespaces when bundle install modes are {AllNamespaces, SingleNamespace, OwnNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedTargetNamespaces: []string{corev1.NamespaceAll}, + }, + { + name: "No default when bundle install modes are {SingleNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace}, + expectedErrMsg: "exactly one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {OwnNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace}, + expectedErrMsg: "exactly one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeMultiNamespace}, + expectedErrMsg: "at least one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {SingleNamespace, OwnNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace}, + expectedErrMsg: "exactly one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {SingleNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedErrMsg: "at least one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {OwnNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedErrMsg: "at least one target namespace must be specified", + }, + { + name: "No default when bundle install modes are {SingleNamespace, OwnNamespace, MultiNamespace}", + supportedInstallModes: []v1alpha1.InstallModeType{v1alpha1.InstallModeTypeSingleNamespace, v1alpha1.InstallModeTypeOwnNamespace, v1alpha1.InstallModeTypeMultiNamespace}, + expectedErrMsg: "at least one target namespace must be specified", + }, + } { + t.Run(tc.name, func(t *testing.T) { + renderer := render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + require.Equal(t, tc.expectedTargetNamespaces, opts.TargetNamespaces) + return nil, nil + }, + }, + } + _, err := renderer.Render(bundle.RegistryV1{ + CSV: clusterserviceversion.Builder(). + WithName("test"). + WithInstallModeSupportFor(tc.supportedInstallModes...).Build(), + }, "some-namespace") + if tc.expectedErrMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErrMsg) + } else { + require.NoError(t, err) + } + }) + } +} + +func Test_BundleRenderer_ValidatesRenderOptions(t *testing.T) { + for _, tc := range []struct { + name string + installNamespace string + csv v1alpha1.ClusterServiceVersion + opts []render.Option + err error + }{ + { + name: "accepts empty targetNamespaces (because it is ignored)", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + opts: []render.Option{ + render.WithTargetNamespaces(), + }, + }, { + name: "rejects nil unique name generator", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + opts: []render.Option{ + render.WithUniqueNameGenerator(nil), + }, + err: errors.New("invalid option(s): unique name generator must be specified"), + }, { + name: "rejects all namespace install if AllNamespaces install mode is not supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces(corev1.NamespaceAll), + }, + err: errors.New("invalid option(s): invalid target namespaces []: supported install modes [SingleNamespace] do not support targeting all namespaces"), + }, { + name: "rejects own namespace install if only AllNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("install-namespace"), + }, + err: errors.New("invalid option(s): invalid target namespaces [install-namespace]: supported install modes [AllNamespaces] do not support targeting own namespace"), + }, { + name: "rejects install out of own namespace if only OwnNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("not-install-namespace"), + }, + err: errors.New("invalid option(s): invalid target namespaces [not-install-namespace]: supported install modes [OwnNamespace] do not support target namespaces [not-install-namespace]"), + }, { + name: "rejects multi-namespace install if MultiNamespace install mode is not supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("ns1", "ns2", "ns3"), + }, + err: errors.New("invalid option(s): invalid target namespaces [ns1 ns2 ns3]: supported install modes [AllNamespaces] do not support target namespaces [ns1 ns2 ns3]"), + }, { + name: "rejects if bundle supports no install modes", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().Build(), + opts: []render.Option{ + render.WithTargetNamespaces("some-namespace"), + }, + err: errors.New("invalid option(s): invalid target namespaces [some-namespace]: supported install modes [] do not support target namespaces [some-namespace]"), + }, { + name: "accepts all namespace render if AllNamespaces install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + opts: []render.Option{ + render.WithTargetNamespaces(""), + }, + }, { + name: "accepts install namespace render if SingleNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("some-namespace"), + }, + }, { + name: "accepts all install namespace render if OwnNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeOwnNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("install-namespace"), + }, + }, { + name: "accepts single namespace render if SingleNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("some-namespace"), + }, + }, { + name: "accepts multi namespace render if MultiNamespace install mode is supported", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeMultiNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("n1", "n2", "n3"), + }, + }, { + name: "reject multi namespace render if OwnNamespace install mode is not supported and target namespaces include install namespace", + installNamespace: "install-namespace", + csv: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeMultiNamespace).Build(), + opts: []render.Option{ + render.WithTargetNamespaces("n1", "n2", "n3", "install-namespace"), + }, + err: errors.New("invalid option(s): invalid target namespaces [n1 n2 n3 install-namespace]: supported install modes [MultiNamespace] do not support targeting own namespace"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + renderer := render.BundleRenderer{} + _, err := renderer.Render( + bundle.RegistryV1{CSV: tc.csv}, + tc.installNamespace, + tc.opts..., + ) + if tc.err == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, tc.err.Error(), err.Error()) + } + }) + } +} + +func Test_BundleRenderer_AppliesUserOptions(t *testing.T) { + isOptionApplied := false + _, _ = render.BundleRenderer{}.Render(bundle.RegistryV1{}, "install-namespace", func(options *render.Options) { + isOptionApplied = true + }) + require.True(t, isOptionApplied) +} + +func Test_WithTargetNamespaces(t *testing.T) { + opts := &render.Options{ + TargetNamespaces: []string{"target-namespace"}, + } + render.WithTargetNamespaces("a", "b", "c")(opts) + require.Equal(t, []string{"a", "b", "c"}, opts.TargetNamespaces) +} + +func Test_WithUniqueNameGenerator(t *testing.T) { + opts := &render.Options{ + UniqueNameGenerator: render.DefaultUniqueNameGenerator, + } + render.WithUniqueNameGenerator(func(s string, i interface{}) string { + return "a man needs a name" + })(opts) + generatedName := opts.UniqueNameGenerator("", nil) + require.Equal(t, "a man needs a name", generatedName) +} + +func Test_WithCertificateProvide(t *testing.T) { + opts := &render.Options{} + expectedCertProvider := FakeCertProvider{} + render.WithCertificateProvider(expectedCertProvider)(opts) + require.Equal(t, expectedCertProvider, opts.CertificateProvider) +} + +func Test_BundleRenderer_CallsResourceGenerators(t *testing.T) { + renderer := render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&corev1.Namespace{}, &corev1.Service{}}, nil + }, + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&appsv1.Deployment{}}, nil + }, + }, + } + objs, err := renderer.Render( + bundle.RegistryV1{ + CSV: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + }, "") + require.NoError(t, err) + require.Equal(t, []client.Object{&corev1.Namespace{}, &corev1.Service{}, &appsv1.Deployment{}}, objs) +} + +func Test_BundleRenderer_ReturnsResourceGeneratorErrors(t *testing.T) { + renderer := render.BundleRenderer{ + ResourceGenerators: []render.ResourceGenerator{ + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return []client.Object{&corev1.Namespace{}, &corev1.Service{}}, nil + }, + func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { + return nil, fmt.Errorf("generator error") + }, + }, + } + objs, err := renderer.Render( + bundle.RegistryV1{ + CSV: clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces).Build(), + }, "") + require.Nil(t, objs) + require.Error(t, err) + require.Contains(t, err.Error(), "generator error") +} + +func Test_BundleValidatorCallsAllValidationFnsInOrder(t *testing.T) { + actual := "" + val := render.BundleValidator{ + func(v1 *bundle.RegistryV1) []error { + actual += "h" + return nil + }, + func(v1 *bundle.RegistryV1) []error { + actual += "i" + return nil + }, + } + require.NoError(t, val.Validate(nil)) + require.Equal(t, "hi", actual) +} diff --git a/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs.go b/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs.go new file mode 100644 index 0000000000..b9d2d8c25a --- /dev/null +++ b/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs.go @@ -0,0 +1,145 @@ +package bundlefs + +import ( + "fmt" + "path/filepath" + "strings" + "testing/fstest" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-registry/alpha/property" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" + registry "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/operator-registry" +) + +const ( + BundlePathAnnotations = "metadata/annotations.yaml" + BundlePathProperties = "metadata/properties.yaml" + BundlePathManifests = "manifests" +) + +// BundleFSBuilder builds a registry+v1 bundle filesystem +type BundleFSBuilder interface { + WithPackageName(packageName string) BundleFSBuilder + WithChannels(channels ...string) BundleFSBuilder + WithDefaultChannel(channel string) BundleFSBuilder + WithBundleProperty(propertyType string, value string) BundleFSBuilder + WithBundleResource(resourceName string, resource client.Object) BundleFSBuilder + WithCSV(csv v1alpha1.ClusterServiceVersion) BundleFSBuilder + Build() fstest.MapFS +} + +// bundleFSBuilder builds a registry+v1 bundle filesystem +type bundleFSBuilder struct { + annotations *registry.Annotations + properties []property.Property + resources map[string]client.Object +} + +func Builder() BundleFSBuilder { + return &bundleFSBuilder{} +} + +// WithPackageName is an option for NewBundleFS used to set the package name annotation in the +// bundle filesystem metadata/annotations.yaml file +func (b *bundleFSBuilder) WithPackageName(packageName string) BundleFSBuilder { + if b.annotations == nil { + b.annotations = ®istry.Annotations{} + } + b.annotations.PackageName = packageName + return b +} + +// WithChannels is an option for NewBundleFS used to set the channels annotation in the +// bundle filesystem metadata/annotations.yaml file +func (b *bundleFSBuilder) WithChannels(channels ...string) BundleFSBuilder { + if b.annotations == nil { + b.annotations = ®istry.Annotations{} + } + b.annotations.Channels = strings.Join(channels, ",") + return b +} + +// WithDefaultChannel is an option for NewBundleFS used to set the channel annotation in the +// bundle filesystem metadata/annotations.yaml file +func (b *bundleFSBuilder) WithDefaultChannel(channel string) BundleFSBuilder { + if b.annotations == nil { + b.annotations = ®istry.Annotations{} + } + b.annotations.DefaultChannelName = channel + return b +} + +// WithBundleProperty is an options for NewBundleFS used to add a property to the list of properties +// in the bundle filesystem metadata/properties.yaml file +func (b *bundleFSBuilder) WithBundleProperty(propertyType string, value string) BundleFSBuilder { + b.properties = append(b.properties, property.Property{ + Type: propertyType, + Value: []byte(`"` + value + `"`), + }) + return b +} + +// WithBundleResource is an option for NewBundleFS use to add the yaml representation of resource to the +// path manifests/.yaml on the bundles filesystem +func (b *bundleFSBuilder) WithBundleResource(resourceName string, resource client.Object) BundleFSBuilder { + if b.resources == nil { + b.resources = make(map[string]client.Object) + } + b.resources[resourceName] = resource + return b +} + +// WithCSV is an optiona for NewBundleFS used to add the yaml representation of csv to the +// path manifests/csv.yaml on the bundle filesystem +func (b *bundleFSBuilder) WithCSV(csv v1alpha1.ClusterServiceVersion) BundleFSBuilder { + if b.resources == nil { + b.resources = make(map[string]client.Object) + } + b.resources["csv.yaml"] = &csv + return b +} + +// Build creates a registry+v1 bundle filesystem with the applied options +// By default, an empty registry+v1 bundle filesystem will be returned +func (b *bundleFSBuilder) Build() fstest.MapFS { + bundleFS := fstest.MapFS{} + + // Add annotations metadata + if b.annotations != nil { + annotationsYml, err := yaml.Marshal(registry.AnnotationsFile{ + Annotations: *b.annotations, + }) + if err != nil { + panic(fmt.Errorf("error building bundle fs: %w", err)) + } + bundleFS[BundlePathAnnotations] = &fstest.MapFile{Data: annotationsYml} + } + + // Add property metadata + if len(b.properties) > 0 { + propertiesYml, err := yaml.Marshal(source.RegistryV1Properties{ + Properties: b.properties, + }) + if err != nil { + panic(fmt.Errorf("error building bundle fs: %w", err)) + } + bundleFS[BundlePathProperties] = &fstest.MapFile{Data: propertiesYml} + } + + // Add resources + for name, obj := range b.resources { + resourcePath := filepath.Join(BundlePathManifests, name) + resourceYml, err := yaml.Marshal(obj) + if err != nil { + panic(fmt.Errorf("error building bundle fs: %w", err)) + } + bundleFS[resourcePath] = &fstest.MapFile{Data: resourceYml} + } + + return bundleFS +} diff --git a/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs_test.go b/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs_test.go new file mode 100644 index 0000000000..74e8410f35 --- /dev/null +++ b/internal/operator-controller/rukpak/util/testing/bundlefs/bundlefs_test.go @@ -0,0 +1,110 @@ +package bundlefs_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/bundlefs" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_BundleFSBuilder(t *testing.T) { + t.Run("returns empty bundle file system by default", func(t *testing.T) { + bundleFs := bundlefs.Builder().Build() + assert.Empty(t, bundleFs) + }) + + t.Run("WithPackageName sets the bundle package annotation", func(t *testing.T) { + bundleFs := bundlefs.Builder().WithPackageName("test").Build() + require.Contains(t, bundleFs, "metadata/annotations.yaml") + require.Equal(t, []byte(`annotations: + operators.operatorframework.io.bundle.channel.default.v1: "" + operators.operatorframework.io.bundle.channels.v1: "" + operators.operatorframework.io.bundle.package.v1: test +`), bundleFs["metadata/annotations.yaml"].Data) + }) + + t.Run("WithChannels sets the bundle channels annotation", func(t *testing.T) { + bundleFs := bundlefs.Builder().WithChannels("alpha", "beta", "stable").Build() + require.Contains(t, bundleFs, "metadata/annotations.yaml") + require.Equal(t, []byte(`annotations: + operators.operatorframework.io.bundle.channel.default.v1: "" + operators.operatorframework.io.bundle.channels.v1: alpha,beta,stable + operators.operatorframework.io.bundle.package.v1: "" +`), bundleFs["metadata/annotations.yaml"].Data) + }) + + t.Run("WithDefaultChannel sets the bundle default channel annotation", func(t *testing.T) { + bundleFs := bundlefs.Builder().WithDefaultChannel("stable").Build() + require.Contains(t, bundleFs, "metadata/annotations.yaml") + require.Equal(t, []byte(`annotations: + operators.operatorframework.io.bundle.channel.default.v1: stable + operators.operatorframework.io.bundle.channels.v1: "" + operators.operatorframework.io.bundle.package.v1: "" +`), bundleFs["metadata/annotations.yaml"].Data) + }) + + t.Run("WithBundleProperty sets the bundle properties", func(t *testing.T) { + bundleFs := bundlefs.Builder(). + WithBundleProperty("foo", "bar"). + WithBundleProperty("key", "value"). + Build() + + require.Contains(t, bundleFs, "metadata/properties.yaml") + require.Equal(t, []byte(`properties: +- type: foo + value: bar +- type: key + value: value +`), bundleFs["metadata/properties.yaml"].Data) + }) + + t.Run("WithBundleResource adds a resource to the manifests directory", func(t *testing.T) { + bundleFs := bundlefs.Builder().WithBundleResource("service.yaml", &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }).Build() + require.Contains(t, bundleFs, "manifests/service.yaml") + require.Equal(t, []byte(`apiVersion: v1 +kind: Service +metadata: + name: test +spec: {} +status: + loadBalancer: {} +`), bundleFs["manifests/service.yaml"].Data) + }) + + t.Run("WithCSV adds a csv to the manifests directory", func(t *testing.T) { + bundleFs := bundlefs.Builder().WithCSV(clusterserviceversion.Builder().WithName("some-csv").Build()).Build() + require.Contains(t, bundleFs, "manifests/csv.yaml") + require.Equal(t, []byte(`apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: some-csv +spec: + apiservicedefinitions: {} + cleanup: + enabled: false + customresourcedefinitions: {} + displayName: "" + install: + spec: + deployments: null + strategy: "" + provider: {} + version: 0.0.0 +status: + cleanup: {} +`), bundleFs["manifests/csv.yaml"].Data) + }) +} diff --git a/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder.go b/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder.go new file mode 100644 index 0000000000..e7ae2195d9 --- /dev/null +++ b/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder.go @@ -0,0 +1,103 @@ +package clusterserviceversion + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" +) + +var installModes = []v1alpha1.InstallModeType{ + v1alpha1.InstallModeTypeAllNamespaces, + v1alpha1.InstallModeTypeSingleNamespace, + v1alpha1.InstallModeTypeMultiNamespace, + v1alpha1.InstallModeTypeOwnNamespace, +} + +// ClusterServiceVersionBuilder build a ClusterServiceVersion resource +type ClusterServiceVersionBuilder interface { + WithName(name string) ClusterServiceVersionBuilder + WithStrategyDeploymentSpecs(strategyDeploymentSpecs ...v1alpha1.StrategyDeploymentSpec) ClusterServiceVersionBuilder + WithAnnotations(annotations map[string]string) ClusterServiceVersionBuilder + WithPermissions(permissions ...v1alpha1.StrategyDeploymentPermissions) ClusterServiceVersionBuilder + WithClusterPermissions(permissions ...v1alpha1.StrategyDeploymentPermissions) ClusterServiceVersionBuilder + WithOwnedCRDs(crdDesc ...v1alpha1.CRDDescription) ClusterServiceVersionBuilder + WithInstallModeSupportFor(installModeType ...v1alpha1.InstallModeType) ClusterServiceVersionBuilder + WithWebhookDefinitions(webhookDefinitions ...v1alpha1.WebhookDescription) ClusterServiceVersionBuilder + WithOwnedAPIServiceDescriptions(ownedAPIServiceDescriptions ...v1alpha1.APIServiceDescription) ClusterServiceVersionBuilder + Build() v1alpha1.ClusterServiceVersion +} + +// Builder creates a new ClusterServiceVersionBuilder for building ClusterServiceVersion resources +func Builder() ClusterServiceVersionBuilder { + return &clusterServiceVersionBuilder{ + csv: v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "ClusterServiceVersion", + }, + }, + } +} + +type clusterServiceVersionBuilder struct { + csv v1alpha1.ClusterServiceVersion +} + +//nolint:unparam +func (b *clusterServiceVersionBuilder) WithName(name string) ClusterServiceVersionBuilder { + b.csv.Name = name + return b +} + +func (b *clusterServiceVersionBuilder) WithStrategyDeploymentSpecs(strategyDeploymentSpecs ...v1alpha1.StrategyDeploymentSpec) ClusterServiceVersionBuilder { + b.csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs = strategyDeploymentSpecs + return b +} + +func (b *clusterServiceVersionBuilder) WithAnnotations(annotations map[string]string) ClusterServiceVersionBuilder { + b.csv.Annotations = annotations + return b +} + +func (b *clusterServiceVersionBuilder) WithPermissions(permissions ...v1alpha1.StrategyDeploymentPermissions) ClusterServiceVersionBuilder { + b.csv.Spec.InstallStrategy.StrategySpec.Permissions = permissions + return b +} + +func (b *clusterServiceVersionBuilder) WithClusterPermissions(permissions ...v1alpha1.StrategyDeploymentPermissions) ClusterServiceVersionBuilder { + b.csv.Spec.InstallStrategy.StrategySpec.ClusterPermissions = permissions + return b +} + +func (b *clusterServiceVersionBuilder) WithOwnedCRDs(crdDesc ...v1alpha1.CRDDescription) ClusterServiceVersionBuilder { + b.csv.Spec.CustomResourceDefinitions.Owned = crdDesc + return b +} + +func (b *clusterServiceVersionBuilder) WithInstallModeSupportFor(installModeType ...v1alpha1.InstallModeType) ClusterServiceVersionBuilder { + supportedInstallModes := sets.New(installModeType...) + csvInstallModes := make([]v1alpha1.InstallMode, 0, len(installModeType)) + for _, t := range installModes { + csvInstallModes = append(csvInstallModes, v1alpha1.InstallMode{ + Type: t, + Supported: supportedInstallModes.Has(t), + }) + } + b.csv.Spec.InstallModes = csvInstallModes + return b +} + +func (b *clusterServiceVersionBuilder) WithWebhookDefinitions(webhookDefinitions ...v1alpha1.WebhookDescription) ClusterServiceVersionBuilder { + b.csv.Spec.WebhookDefinitions = webhookDefinitions + return b +} + +func (b *clusterServiceVersionBuilder) WithOwnedAPIServiceDescriptions(ownedAPIServiceDescriptions ...v1alpha1.APIServiceDescription) ClusterServiceVersionBuilder { + b.csv.Spec.APIServiceDefinitions.Owned = ownedAPIServiceDescriptions + return b +} + +func (b *clusterServiceVersionBuilder) Build() v1alpha1.ClusterServiceVersion { + return b.csv +} diff --git a/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder_test.go b/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder_test.go new file mode 100644 index 0000000000..45bad4e53c --- /dev/null +++ b/internal/operator-controller/rukpak/util/testing/clusterserviceversion/builder_test.go @@ -0,0 +1,215 @@ +package clusterserviceversion_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/api/pkg/operators/v1alpha1" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util/testing/clusterserviceversion" +) + +func Test_Builder(t *testing.T) { + t.Run("builds an empty csv by default", func(t *testing.T) { + obj := clusterserviceversion.Builder().Build() + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + }, obj) + }) + + t.Run("WithName sets csv .metadata.name", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithName("some-name").Build() + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + }, + }, obj) + }) + + t.Run("WithStrategyDeploymentSpecs sets csv .spec.install.spec.deployments", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithStrategyDeploymentSpecs( + v1alpha1.StrategyDeploymentSpec{ + Name: "spec-one", + }, + v1alpha1.StrategyDeploymentSpec{ + Name: "spec-two", + }, + ).Build() + + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: v1alpha1.NamedInstallStrategy{ + StrategySpec: v1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: []v1alpha1.StrategyDeploymentSpec{ + { + Name: "spec-one", + }, + { + Name: "spec-two", + }, + }, + }, + }, + }, + }, obj) + }) + + t.Run("WithPermissions sets csv .spec.install.spec.permissions", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + }, + ).Build() + + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: v1alpha1.NamedInstallStrategy{ + StrategySpec: v1alpha1.StrategyDetailsDeployment{ + Permissions: []v1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: "service-account", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"list", "watch"}, + }, + }, + }, + { + ServiceAccountName: "", + }, + }, + }, + }, + }, + }, obj) + }) + + t.Run("WithClusterPermissions sets csv .spec.install.spec.clusterPermissions", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithClusterPermissions( + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "service-account", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"list", "watch"}, + }, + }, + }, + v1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: "", + }, + ).Build() + + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + InstallStrategy: v1alpha1.NamedInstallStrategy{ + StrategySpec: v1alpha1.StrategyDetailsDeployment{ + ClusterPermissions: []v1alpha1.StrategyDeploymentPermissions{ + { + ServiceAccountName: "service-account", + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"list", "watch"}, + }, + }, + }, + { + ServiceAccountName: "", + }, + }, + }, + }, + }, + }, obj) + }) + + t.Run("WithClusterPermissions sets csv .spec.customresourcedefinitions.owned", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithOwnedCRDs( + v1alpha1.CRDDescription{Name: "a.crd.something"}, + v1alpha1.CRDDescription{Name: "b.crd.something"}, + ).Build() + + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{ + Owned: []v1alpha1.CRDDescription{ + {Name: "a.crd.something"}, + {Name: "b.crd.something"}, + }, + }, + }, + }, obj) + }) + + t.Run("WithInstallModeSupportFor adds all install modes to .spec.installModes and sets supported to true for the given supported install modes", func(t *testing.T) { + obj := clusterserviceversion.Builder().WithInstallModeSupportFor(v1alpha1.InstallModeTypeAllNamespaces, v1alpha1.InstallModeTypeSingleNamespace).Build() + + require.Equal(t, v1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterServiceVersion", + APIVersion: v1alpha1.SchemeGroupVersion.String(), + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + InstallModes: []v1alpha1.InstallMode{ + { + Type: v1alpha1.InstallModeTypeAllNamespaces, + Supported: true, + }, + { + Type: v1alpha1.InstallModeTypeSingleNamespace, + Supported: true, + }, + { + Type: v1alpha1.InstallModeTypeMultiNamespace, + Supported: false, + }, + { + Type: v1alpha1.InstallModeTypeOwnNamespace, + Supported: false, + }, + }, + }, + }, obj) + }) +} diff --git a/internal/operator-controller/rukpak/util/testing/testing.go b/internal/operator-controller/rukpak/util/testing/testing.go new file mode 100644 index 0000000000..2670091a19 --- /dev/null +++ b/internal/operator-controller/rukpak/util/testing/testing.go @@ -0,0 +1,43 @@ +package testing + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" +) + +type FakeCertProvider struct { + InjectCABundleFn func(obj client.Object, cfg render.CertificateProvisionerConfig) error + AdditionalObjectsFn func(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) + GetCertSecretInfoFn func(cfg render.CertificateProvisionerConfig) render.CertSecretInfo +} + +func (f FakeCertProvider) InjectCABundle(obj client.Object, cfg render.CertificateProvisionerConfig) error { + return f.InjectCABundleFn(obj, cfg) +} + +func (f FakeCertProvider) AdditionalObjects(cfg render.CertificateProvisionerConfig) ([]unstructured.Unstructured, error) { + return f.AdditionalObjectsFn(cfg) +} + +func (f FakeCertProvider) GetCertSecretInfo(cfg render.CertificateProvisionerConfig) render.CertSecretInfo { + return f.GetCertSecretInfoFn(cfg) +} + +type FakeBundleSource func() (bundle.RegistryV1, error) + +func (f FakeBundleSource) GetBundle() (bundle.RegistryV1, error) { + return f() +} + +func ToUnstructuredT(t *testing.T, obj client.Object) *unstructured.Unstructured { + u, err := util.ToUnstructured(obj) + require.NoError(t, err) + return u +} diff --git a/internal/operator-controller/rukpak/util/util.go b/internal/operator-controller/rukpak/util/util.go new file mode 100644 index 0000000000..067722c47e --- /dev/null +++ b/internal/operator-controller/rukpak/util/util.go @@ -0,0 +1,78 @@ +package util + +import ( + "errors" + "fmt" + "io" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const maxNameLength = 63 + +func ObjectNameForBaseAndSuffix(base string, suffix string) string { + if len(base)+len(suffix) > maxNameLength { + base = base[:maxNameLength-len(suffix)-1] + } + return fmt.Sprintf("%s-%s", base, suffix) +} + +// ToUnstructured converts obj into an Unstructured. It expects the obj's gvk to be defined. If it is not, +// an error will be returned. +func ToUnstructured(obj client.Object) (*unstructured.Unstructured, error) { + if obj == nil { + return nil, errors.New("object is nil") + } + + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, errors.New("object has no kind") + } + if len(gvk.Version) == 0 { + return nil, errors.New("object has no version") + } + + var u unstructured.Unstructured + uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("convert %s %q to unstructured: %w", gvk.Kind, obj.GetName(), err) + } + unstructured.RemoveNestedField(uObj, "metadata", "creationTimestamp") + u.Object = uObj + u.SetGroupVersionKind(gvk) + return &u, nil +} + +func MergeMaps(maps ...map[string]string) map[string]string { + out := map[string]string{} + for _, m := range maps { + for k, v := range m { + out[k] = v + } + } + return out +} + +func ManifestObjects(r io.Reader, name string) ([]client.Object, error) { + result := resource.NewLocalBuilder().Flatten().Unstructured().Stream(r, name).Do() + if err := result.Err(); err != nil { + return nil, err + } + infos, err := result.Infos() + if err != nil { + return nil, err + } + return infosToObjects(infos), nil +} + +func infosToObjects(infos []*resource.Info) []client.Object { + objects := make([]client.Object, 0, len(infos)) + for _, info := range infos { + clientObject := info.Object.(client.Object) + objects = append(objects, clientObject) + } + return objects +} diff --git a/internal/rukpak/util/util_test.go b/internal/operator-controller/rukpak/util/util_test.go similarity index 68% rename from internal/rukpak/util/util_test.go rename to internal/operator-controller/rukpak/util/util_test.go index 5a076ed6ae..25073f0ce4 100644 --- a/internal/rukpak/util/util_test.go +++ b/internal/operator-controller/rukpak/util/util_test.go @@ -13,9 +13,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/operator-framework/operator-controller/internal/rukpak/util" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" ) +func Test_ObjectNameForBaseAndSuffix(t *testing.T) { + name := util.ObjectNameForBaseAndSuffix("my.object.thing.has.a.really.really.really.really.really.long.name", "suffix") + require.Len(t, name, 63) + require.Equal(t, "my.object.thing.has.a.really.really.really.really.really-suffix", name) +} + func TestMergeMaps(t *testing.T) { tests := []struct { name string @@ -50,15 +56,6 @@ func TestMergeMaps(t *testing.T) { } } -// Mock reader for testing that always returns an error when Read is called -type errorReader struct { - io.Reader -} - -func (m errorReader) Read(p []byte) (int, error) { - return 0, errors.New("Oh no!") -} - func TestManifestObjects(t *testing.T) { tests := []struct { name string @@ -130,7 +127,7 @@ spec: if tc.wantErr { require.Error(t, err) } else { - assert.Equal(t, len(objs), len(tc.expectObjects)) + assert.Len(t, tc.expectObjects, len(objs)) // Sort the objs by name for easy direct comparison sort.Slice(objs, func(i int, j int) bool { return objs[i].GetName() < objs[j].GetName() @@ -146,3 +143,54 @@ spec: }) } } + +func Test_ToUnstructured(t *testing.T) { + for _, tc := range []struct { + name string + obj client.Object + err error + }{ + { + name: "converts object to unstructured", + obj: &corev1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "my-service", Namespace: "my-namespace"}, + }, + }, { + name: "fails if object doesn't define kind", + obj: &corev1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "my-service", Namespace: "my-namespace"}, + }, + err: errors.New("object has no kind"), + }, { + name: "fails if object doesn't define version", + obj: &corev1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, + ObjectMeta: metav1.ObjectMeta{Name: "my-service", Namespace: "my-namespace"}, + }, + err: errors.New("object has no version"), + }, { + name: "fails if object is nil", + err: errors.New("object is nil"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + out, err := util.ToUnstructured(tc.obj) + if tc.err != nil { + require.Error(t, err) + } else { + assert.Equal(t, tc.obj.GetObjectKind().GroupVersionKind(), out.GroupVersionKind()) + } + }) + } +} + +// Mock reader for testing that always returns an error when Read is called +type errorReader struct { + io.Reader +} + +func (m errorReader) Read(p []byte) (int, error) { + return 0, errors.New("Oh no!") +} diff --git a/internal/scheme/scheme.go b/internal/operator-controller/scheme/scheme.go similarity index 65% rename from internal/scheme/scheme.go rename to internal/operator-controller/scheme/scheme.go index 933d89b052..bb8d44ef78 100644 --- a/internal/scheme/scheme.go +++ b/internal/operator-controller/scheme/scheme.go @@ -7,17 +7,15 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) var Scheme = runtime.NewScheme() func init() { utilruntime.Must(clientgoscheme.AddToScheme(Scheme)) - utilruntime.Must(ocv1alpha1.AddToScheme(Scheme)) - utilruntime.Must(catalogd.AddToScheme(Scheme)) + utilruntime.Must(ocv1.AddToScheme(Scheme)) + utilruntime.Must(ocv1.AddToScheme(Scheme)) utilruntime.Must(appsv1.AddToScheme(Scheme)) utilruntime.Must(corev1.AddToScheme(Scheme)) //+kubebuilder:scaffold:scheme diff --git a/internal/resolve/resolver.go b/internal/resolve/resolver.go deleted file mode 100644 index de9b952b01..0000000000 --- a/internal/resolve/resolver.go +++ /dev/null @@ -1,21 +0,0 @@ -package resolve - -import ( - "context" - - bsemver "github.com/blang/semver/v4" - - "github.com/operator-framework/operator-registry/alpha/declcfg" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" -) - -type Resolver interface { - Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) -} - -type Func func(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) - -func (f Func) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { - return f(ctx, ext, installedBundle) -} diff --git a/internal/rukpak/convert/registryv1.go b/internal/rukpak/convert/registryv1.go deleted file mode 100644 index 0acdd0d6d8..0000000000 --- a/internal/rukpak/convert/registryv1.go +++ /dev/null @@ -1,423 +0,0 @@ -package convert - -import ( - "context" - "crypto/sha256" - "encoding/json" - "fmt" - "io/fs" - "path/filepath" - "strings" - - "helm.sh/helm/v3/pkg/chart" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle" - - registry "github.com/operator-framework/operator-controller/internal/rukpak/operator-registry" - "github.com/operator-framework/operator-controller/internal/rukpak/util" -) - -type RegistryV1 struct { - PackageName string - CSV v1alpha1.ClusterServiceVersion - Others []unstructured.Unstructured -} - -type Plain struct { - Objects []client.Object -} - -func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace string, watchNamespaces []string) (*chart.Chart, error) { - l := log.FromContext(ctx) - - reg := RegistryV1{} - fileData, err := fs.ReadFile(rv1, filepath.Join("metadata", "annotations.yaml")) - if err != nil { - return nil, err - } - annotationsFile := registry.AnnotationsFile{} - if err := yaml.Unmarshal(fileData, &annotationsFile); err != nil { - return nil, err - } - reg.PackageName = annotationsFile.Annotations.PackageName - - const manifestsDir = "manifests" - if err := fs.WalkDir(rv1, manifestsDir, func(path string, e fs.DirEntry, err error) error { - if err != nil { - return err - } - if e.IsDir() { - if path == manifestsDir { - return nil - } - return fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, path) - } - manifestFile, err := rv1.Open(path) - if err != nil { - return err - } - defer func() { - if err := manifestFile.Close(); err != nil { - l.Error(err, "error closing file", "path", path) - } - }() - - result := resource.NewLocalBuilder().Unstructured().Flatten().Stream(manifestFile, path).Do() - if err := result.Err(); err != nil { - return err - } - if err := result.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - switch info.Object.GetObjectKind().GroupVersionKind().Kind { - case "ClusterServiceVersion": - csv := v1alpha1.ClusterServiceVersion{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(info.Object.(*unstructured.Unstructured).Object, &csv); err != nil { - return err - } - reg.CSV = csv - default: - reg.Others = append(reg.Others, *info.Object.(*unstructured.Unstructured)) - } - return nil - }); err != nil { - return fmt.Errorf("error parsing objects in %q: %v", path, err) - } - return nil - }); err != nil { - return nil, err - } - - return toChart(reg, installNamespace, watchNamespaces) -} - -func toChart(in RegistryV1, installNamespace string, watchNamespaces []string) (*chart.Chart, error) { - plain, err := Convert(in, installNamespace, watchNamespaces) - if err != nil { - return nil, err - } - - chrt := &chart.Chart{Metadata: &chart.Metadata{}} - chrt.Metadata.Annotations = in.CSV.GetAnnotations() - for _, obj := range plain.Objects { - jsonData, err := json.Marshal(obj) - if err != nil { - return nil, err - } - hash := sha256.Sum256(jsonData) - chrt.Templates = append(chrt.Templates, &chart.File{ - Name: fmt.Sprintf("object-%x.json", hash[0:8]), - Data: jsonData, - }) - } - - return chrt, nil -} - -func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNamespace string, targetNamespaces []string) error { - set := sets.New[string](targetNamespaces...) - switch { - case set.Len() == 0 || (set.Len() == 1 && set.Has("")): - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces)) { - return nil - } - return fmt.Errorf("supported install modes %v do not support targeting all namespaces", sets.List(supportedInstallModes)) - case set.Len() == 1 && !set.Has(""): - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeSingleNamespace)) { - return nil - } - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeOwnNamespace)) && targetNamespaces[0] == installNamespace { - return nil - } - default: - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeMultiNamespace)) && !set.Has("") { - return nil - } - } - return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[string](supportedInstallModes), targetNamespaces) -} - -func saNameOrDefault(saName string) string { - if saName == "" { - return "default" - } - return saName -} - -func Convert(in RegistryV1, installNamespace string, targetNamespaces []string) (*Plain, error) { - if installNamespace == "" { - installNamespace = in.CSV.Annotations["operatorframework.io/suggested-namespace"] - } - if installNamespace == "" { - installNamespace = fmt.Sprintf("%s-system", in.PackageName) - } - supportedInstallModes := sets.New[string]() - for _, im := range in.CSV.Spec.InstallModes { - if im.Supported { - supportedInstallModes.Insert(string(im.Type)) - } - } - if len(targetNamespaces) == 0 { - if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces)) { - targetNamespaces = []string{""} - } else if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeOwnNamespace)) { - targetNamespaces = []string{installNamespace} - } - } - - if err := validateTargetNamespaces(supportedInstallModes, installNamespace, targetNamespaces); err != nil { - return nil, err - } - - if len(in.CSV.Spec.APIServiceDefinitions.Owned) > 0 { - return nil, fmt.Errorf("apiServiceDefintions are not supported") - } - - if len(in.CSV.Spec.WebhookDefinitions) > 0 { - return nil, fmt.Errorf("webhookDefinitions are not supported") - } - - deployments := []appsv1.Deployment{} - serviceAccounts := map[string]corev1.ServiceAccount{} - for _, depSpec := range in.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs { - annotations := util.MergeMaps(in.CSV.Annotations, depSpec.Spec.Template.Annotations) - annotations["olm.targetNamespaces"] = strings.Join(targetNamespaces, ",") - deployments = append(deployments, appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: appsv1.SchemeGroupVersion.String(), - }, - - ObjectMeta: metav1.ObjectMeta{ - Namespace: installNamespace, - Name: depSpec.Name, - Labels: depSpec.Label, - Annotations: annotations, - }, - Spec: depSpec.Spec, - }) - saName := saNameOrDefault(depSpec.Spec.Template.Spec.ServiceAccountName) - serviceAccounts[saName] = newServiceAccount(installNamespace, saName) - } - - // NOTES: - // 1. There's an extra Role for OperatorConditions: get/update/patch; resourceName=csv.name - // - This is managed by the OperatorConditions controller here: https://github.com/operator-framework/operator-lifecycle-manager/blob/9ced412f3e263b8827680dc0ad3477327cd9a508/pkg/controller/operators/operatorcondition_controller.go#L106-L109 - // 2. There's an extra RoleBinding for the above mentioned role. - // - Every SA mentioned in the OperatorCondition.spec.serviceAccounts is a subject for this role binding: https://github.com/operator-framework/operator-lifecycle-manager/blob/9ced412f3e263b8827680dc0ad3477327cd9a508/pkg/controller/operators/operatorcondition_controller.go#L171-L177 - // 3. strategySpec.permissions are _also_ given a clusterrole/clusterrole binding. - // - (for AllNamespaces mode only?) - // - (where does the extra namespaces get/list/watch rule come from?) - - roles := []rbacv1.Role{} - roleBindings := []rbacv1.RoleBinding{} - clusterRoles := []rbacv1.ClusterRole{} - clusterRoleBindings := []rbacv1.ClusterRoleBinding{} - - permissions := in.CSV.Spec.InstallStrategy.StrategySpec.Permissions - clusterPermissions := in.CSV.Spec.InstallStrategy.StrategySpec.ClusterPermissions - allPermissions := append(permissions, clusterPermissions...) - - // Create all the service accounts - for _, permission := range allPermissions { - saName := saNameOrDefault(permission.ServiceAccountName) - if _, ok := serviceAccounts[saName]; !ok { - serviceAccounts[saName] = newServiceAccount(installNamespace, saName) - } - } - - // If we're in AllNamespaces mode, promote the permissions to clusterPermissions - if len(targetNamespaces) == 1 && targetNamespaces[0] == "" { - for _, p := range permissions { - p.Rules = append(p.Rules, rbacv1.PolicyRule{ - Verbs: []string{"get", "list", "watch"}, - APIGroups: []string{corev1.GroupName}, - Resources: []string{"namespaces"}, - }) - } - clusterPermissions = append(clusterPermissions, permissions...) - permissions = nil - } - - for _, ns := range targetNamespaces { - for _, permission := range permissions { - saName := saNameOrDefault(permission.ServiceAccountName) - name, err := generateName(fmt.Sprintf("%s-%s", in.CSV.Name, saName), permission) - if err != nil { - return nil, err - } - roles = append(roles, newRole(ns, name, permission.Rules)) - roleBindings = append(roleBindings, newRoleBinding(ns, name, name, installNamespace, saName)) - } - } - - for _, permission := range clusterPermissions { - saName := saNameOrDefault(permission.ServiceAccountName) - name, err := generateName(fmt.Sprintf("%s-%s", in.CSV.Name, saName), permission) - if err != nil { - return nil, err - } - clusterRoles = append(clusterRoles, newClusterRole(name, permission.Rules)) - clusterRoleBindings = append(clusterRoleBindings, newClusterRoleBinding(name, name, installNamespace, saName)) - } - - objs := []client.Object{} - for _, obj := range serviceAccounts { - obj := obj - if obj.GetName() != "default" { - objs = append(objs, &obj) - } - } - for _, obj := range roles { - obj := obj - objs = append(objs, &obj) - } - for _, obj := range roleBindings { - obj := obj - objs = append(objs, &obj) - } - for _, obj := range clusterRoles { - obj := obj - objs = append(objs, &obj) - } - for _, obj := range clusterRoleBindings { - obj := obj - objs = append(objs, &obj) - } - for _, obj := range in.Others { - obj := obj - supported, namespaced := registrybundle.IsSupported(obj.GetKind()) - if !supported { - return nil, fmt.Errorf("bundle contains unsupported resource: Name: %v, Kind: %v", obj.GetName(), obj.GetKind()) - } - if namespaced { - obj.SetNamespace(installNamespace) - } - objs = append(objs, &obj) - } - for _, obj := range deployments { - obj := obj - objs = append(objs, &obj) - } - return &Plain{Objects: objs}, nil -} - -const maxNameLength = 63 - -func generateName(base string, o interface{}) (string, error) { - hashStr, err := util.DeepHashObject(o) - if err != nil { - return "", err - } - if len(base)+len(hashStr) > maxNameLength { - base = base[:maxNameLength-len(hashStr)-1] - } - - return fmt.Sprintf("%s-%s", base, hashStr), nil -} - -func newServiceAccount(namespace, name string) corev1.ServiceAccount { - return corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - APIVersion: corev1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - } -} - -func newRole(namespace, name string, rules []rbacv1.PolicyRule) rbacv1.Role { - return rbacv1.Role{ - TypeMeta: metav1.TypeMeta{ - Kind: "Role", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Rules: rules, - } -} - -func newClusterRole(name string, rules []rbacv1.PolicyRule) rbacv1.ClusterRole { - return rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - Kind: "ClusterRole", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Rules: rules, - } -} - -func newRoleBinding(namespace, name, roleName, saNamespace string, saNames ...string) rbacv1.RoleBinding { - subjects := make([]rbacv1.Subject, 0, len(saNames)) - for _, saName := range saNames { - subjects = append(subjects, rbacv1.Subject{ - Kind: "ServiceAccount", - Namespace: saNamespace, - Name: saName, - }) - } - return rbacv1.RoleBinding{ - TypeMeta: metav1.TypeMeta{ - Kind: "RoleBinding", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Subjects: subjects, - RoleRef: rbacv1.RoleRef{ - APIGroup: rbacv1.GroupName, - Kind: "Role", - Name: roleName, - }, - } -} - -func newClusterRoleBinding(name, roleName, saNamespace string, saNames ...string) rbacv1.ClusterRoleBinding { - subjects := make([]rbacv1.Subject, 0, len(saNames)) - for _, saName := range saNames { - subjects = append(subjects, rbacv1.Subject{ - Kind: "ServiceAccount", - Namespace: saNamespace, - Name: saName, - }) - } - return rbacv1.ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - Kind: "ClusterRoleBinding", - APIVersion: rbacv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Subjects: subjects, - RoleRef: rbacv1.RoleRef{ - APIGroup: rbacv1.GroupName, - Kind: "ClusterRole", - Name: roleName, - }, - } -} diff --git a/internal/rukpak/convert/registryv1_test.go b/internal/rukpak/convert/registryv1_test.go deleted file mode 100644 index 991d5dbdde..0000000000 --- a/internal/rukpak/convert/registryv1_test.go +++ /dev/null @@ -1,474 +0,0 @@ -package convert - -import ( - "fmt" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - schedulingv1 "k8s.io/api/scheduling/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/operator-registry/alpha/property" -) - -func TestRegistryV1Converter(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "RegstryV1 suite") -} - -var _ = Describe("RegistryV1 Suite", func() { - var _ = Describe("Convert", func() { - var ( - registryv1Bundle RegistryV1 - installNamespace string - targetNamespaces []string - ) - Context("Should set the namespaces of object correctly", func() { - var ( - svc corev1.Service - csv v1alpha1.ClusterServiceVersion - ) - BeforeEach(func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}}, - }, - } - svc = corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testService", - }, - } - svc.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}) - installNamespace = "testInstallNamespace" - }) - - It("should set the namespace to installnamespace if not available", func() { - By("creating a registry v1 bundle") - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).NotTo(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(1)) - - By("verifying if ns has been set correctly") - resObj := containsObject(unstructuredSvc, plainBundle.Objects) - Expect(resObj).NotTo(BeNil()) - Expect(resObj.GetNamespace()).To(BeEquivalentTo(installNamespace)) - }) - - It("should override namespace if already available", func() { - By("creating a registry v1 bundle") - svc.SetNamespace("otherNs") - unstructuredSvc := convertToUnstructured(svc) - unstructuredSvc.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}) - - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).NotTo(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(1)) - - By("verifying if ns has been set correctly") - resObj := containsObject(unstructuredSvc, plainBundle.Objects) - Expect(resObj).NotTo(BeNil()) - Expect(resObj.GetNamespace()).To(BeEquivalentTo(installNamespace)) - }) - - Context("Should error when object is not supported", func() { - It("should error when unsupported GVK is passed", func() { - By("creating an unsupported kind") - event := corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testEvent", - }, - } - - unstructuredEvt := convertToUnstructured(event) - unstructuredEvt.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Event"}) - - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredEvt}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("bundle contains unsupported resource")) - Expect(plainBundle).To(BeNil()) - }) - }) - - Context("Should not set ns cluster scoped object is passed", func() { - It("should not error when cluster scoped obj is passed and not set its namespace", func() { - By("creating an unsupported kind") - pc := schedulingv1.PriorityClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testPriorityClass", - }, - } - - unstructuredpriorityclass := convertToUnstructured(pc) - unstructuredpriorityclass.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PriorityClass"}) - - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredpriorityclass}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).NotTo(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(1)) - - By("verifying if ns has been set correctly") - resObj := containsObject(unstructuredpriorityclass, plainBundle.Objects) - Expect(resObj).NotTo(BeNil()) - Expect(resObj.GetNamespace()).To(BeEmpty()) - }) - }) - }) - - Context("Should generate objects successfully based on target namespaces", func() { - var ( - svc corev1.Service - csv v1alpha1.ClusterServiceVersion - watchNamespaces []string - ) - - BeforeEach(func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - Annotations: map[string]string{ - "olm.properties": fmt.Sprintf("[{\"type\": %s, \"value\": \"%s\"}]", property.TypeConstraint, "value"), - }, - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeMultiNamespace, Supported: true}}, - InstallStrategy: v1alpha1.NamedInstallStrategy{ - StrategySpec: v1alpha1.StrategyDetailsDeployment{ - Permissions: []v1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: "testServiceAccount", - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"test"}, - Resources: []string{"pods"}, - Verbs: []string{"*"}, - }, - }, - }, - }, - }, - }, - }, - } - svc = corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testService", - }, - } - svc.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}) - installNamespace = "testInstallNamespace" - }) - - It("should convert into plain manifests successfully", func() { - By("creating a registry v1 bundle") - watchNamespaces = []string{"testWatchNs1", "testWatchNs2"} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).ShouldNot(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(6)) - }) - - It("should convert into plain manifests successfully with single namespace", func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}}, - InstallStrategy: v1alpha1.NamedInstallStrategy{ - StrategySpec: v1alpha1.StrategyDetailsDeployment{ - Permissions: []v1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: "testServiceAccount", - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"test"}, - Resources: []string{"pods"}, - Verbs: []string{"*"}, - }, - }, - }, - }, - }, - }, - }, - } - - By("creating a registry v1 bundle") - watchNamespaces = []string{"testWatchNs1"} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).ShouldNot(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(4)) - }) - - It("should convert into plain manifests successfully with own namespace", func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}}, - InstallStrategy: v1alpha1.NamedInstallStrategy{ - StrategySpec: v1alpha1.StrategyDetailsDeployment{ - Permissions: []v1alpha1.StrategyDeploymentPermissions{ - { - ServiceAccountName: "testServiceAccount", - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"test"}, - Resources: []string{"pods"}, - Verbs: []string{"*"}, - }, - }, - }, - }, - }, - }, - }, - } - - By("creating a registry v1 bundle") - watchNamespaces = []string{installNamespace} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).NotTo(HaveOccurred()) - - By("verifying if plain bundle has required objects") - Expect(plainBundle).ShouldNot(BeNil()) - Expect(plainBundle.Objects).To(HaveLen(4)) - }) - - It("should error when multinamespace mode is supported with an empty string in target namespaces", func() { - By("creating a registry v1 bundle") - watchNamespaces = []string{"testWatchNs1", ""} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).To(HaveOccurred()) - Expect(plainBundle).To(BeNil()) - }) - - It("should error when single namespace mode is disabled with more than one target namespaces", func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: false}}, - }, - } - - By("creating a registry v1 bundle") - watchNamespaces = []string{"testWatchNs1", "testWatchNs2"} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).To(HaveOccurred()) - Expect(plainBundle).To(BeNil()) - }) - - It("should error when all namespace mode is disabled with target namespace containing an empty string", func() { - csv = v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{ - {Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: false}, - {Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}, - {Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}, - {Type: v1alpha1.InstallModeTypeMultiNamespace, Supported: true}, - }, - }, - } - - By("creating a registry v1 bundle") - watchNamespaces = []string{""} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).To(HaveOccurred()) - Expect(plainBundle).To(BeNil()) - }) - - It("should propagate csv annotations to chart metadata annotation", func() { - By("creating a registry v1 bundle") - watchNamespaces = []string{"testWatchNs1", "testWatchNs2"} - unstructuredSvc := convertToUnstructured(svc) - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - By("converting to helm") - chrt, err := toChart(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).NotTo(HaveOccurred()) - Expect(chrt.Metadata.Annotations["olm.properties"]).NotTo(BeNil()) - }) - }) - - Context("Should enforce limitations", func() { - It("should not allow bundles with webhooks", func() { - By("creating a registry v1 bundle") - csv := v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}}, - WebhookDefinitions: []v1alpha1.WebhookDescription{{ConversionCRDs: []string{"fake-webhook.package-with-webhooks.io"}}}, - }, - } - watchNamespaces := []string{metav1.NamespaceAll} - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).To(MatchError(ContainSubstring("webhookDefinitions are not supported"))) - Expect(plainBundle).To(BeNil()) - }) - - It("should not allow bundles with API service definitions", func() { - By("creating a registry v1 bundle") - csv := v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testCSV", - }, - Spec: v1alpha1.ClusterServiceVersionSpec{ - InstallModes: []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}}, - APIServiceDefinitions: v1alpha1.APIServiceDefinitions{ - Owned: []v1alpha1.APIServiceDescription{{Name: "fake-owned-api-definition"}}, - }, - }, - } - watchNamespaces := []string{metav1.NamespaceAll} - registryv1Bundle = RegistryV1{ - PackageName: "testPkg", - CSV: csv, - } - - By("converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) - Expect(err).To(MatchError(ContainSubstring("apiServiceDefintions are not supported"))) - Expect(plainBundle).To(BeNil()) - }) - }) - }) -}) - -func convertToUnstructured(obj interface{}) unstructured.Unstructured { - unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj) - Expect(err).NotTo(HaveOccurred()) - Expect(unstructuredObj).NotTo(BeNil()) - return unstructured.Unstructured{Object: unstructuredObj} -} - -func containsObject(obj unstructured.Unstructured, result []client.Object) client.Object { - for _, o := range result { - // Since this is a controlled env, comparing only the names is sufficient for now. - // In future, compare GVKs too by ensuring its set on the unstructuredObj. - if o.GetName() == obj.GetName() { - return o - } - } - return nil -} diff --git a/internal/rukpak/preflights/crdupgradesafety/checks.go b/internal/rukpak/preflights/crdupgradesafety/checks.go deleted file mode 100644 index cc7be4a66d..0000000000 --- a/internal/rukpak/preflights/crdupgradesafety/checks.go +++ /dev/null @@ -1,72 +0,0 @@ -package crdupgradesafety - -import ( - "errors" - "fmt" - "slices" - - kappcus "carvel.dev/kapp/pkg/kapp/crdupgradesafety" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - versionhelper "k8s.io/apimachinery/pkg/version" -) - -type ServedVersionValidator struct { - Validations []kappcus.ChangeValidation -} - -func (c *ServedVersionValidator) Validate(old, new apiextensionsv1.CustomResourceDefinition) error { - // If conversion webhook is specified, pass check - if new.Spec.Conversion != nil && new.Spec.Conversion.Strategy == apiextensionsv1.WebhookConverter { - return nil - } - - errs := []error{} - servedVersions := []apiextensionsv1.CustomResourceDefinitionVersion{} - for _, version := range new.Spec.Versions { - if version.Served { - servedVersions = append(servedVersions, version) - } - } - - slices.SortFunc(servedVersions, func(a, b apiextensionsv1.CustomResourceDefinitionVersion) int { - return versionhelper.CompareKubeAwareVersionStrings(a.Name, b.Name) - }) - - for i, oldVersion := range servedVersions[:len(servedVersions)-1] { - for _, newVersion := range servedVersions[i+1:] { - flatOld := kappcus.FlattenSchema(oldVersion.Schema.OpenAPIV3Schema) - flatNew := kappcus.FlattenSchema(newVersion.Schema.OpenAPIV3Schema) - diffs, err := kappcus.CalculateFlatSchemaDiff(flatOld, flatNew) - if err != nil { - errs = append(errs, fmt.Errorf("calculating schema diff between CRD versions %q and %q", oldVersion.Name, newVersion.Name)) - continue - } - - for field, diff := range diffs { - handled := false - for _, validation := range c.Validations { - ok, err := validation(diff) - if err != nil { - errs = append(errs, fmt.Errorf("version upgrade %q to %q, field %q: %w", oldVersion.Name, newVersion.Name, field, err)) - } - if ok { - handled = true - break - } - } - - if !handled { - errs = append(errs, fmt.Errorf("version %q, field %q has unknown change, refusing to determine that change is safe", oldVersion.Name, field)) - } - } - } - } - if len(errs) > 0 { - return errors.Join(errs...) - } - return nil -} - -func (c *ServedVersionValidator) Name() string { - return "ServedVersionValidator" -} diff --git a/internal/rukpak/preflights/crdupgradesafety/crdupgradesafety.go b/internal/rukpak/preflights/crdupgradesafety/crdupgradesafety.go deleted file mode 100644 index 3f91c8c2b4..0000000000 --- a/internal/rukpak/preflights/crdupgradesafety/crdupgradesafety.go +++ /dev/null @@ -1,119 +0,0 @@ -package crdupgradesafety - -import ( - "context" - "errors" - "fmt" - "strings" - - kappcus "carvel.dev/kapp/pkg/kapp/crdupgradesafety" - "helm.sh/helm/v3/pkg/release" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/operator-framework/operator-controller/internal/rukpak/util" -) - -type Option func(p *Preflight) - -func WithValidator(v *kappcus.Validator) Option { - return func(p *Preflight) { - p.validator = v - } -} - -type Preflight struct { - crdClient apiextensionsv1client.CustomResourceDefinitionInterface - validator *kappcus.Validator -} - -func NewPreflight(crdCli apiextensionsv1client.CustomResourceDefinitionInterface, opts ...Option) *Preflight { - changeValidations := []kappcus.ChangeValidation{ - kappcus.EnumChangeValidation, - kappcus.RequiredFieldChangeValidation, - kappcus.MaximumChangeValidation, - kappcus.MaximumItemsChangeValidation, - kappcus.MaximumLengthChangeValidation, - kappcus.MaximumPropertiesChangeValidation, - kappcus.MinimumChangeValidation, - kappcus.MinimumItemsChangeValidation, - kappcus.MinimumLengthChangeValidation, - kappcus.MinimumPropertiesChangeValidation, - kappcus.DefaultValueChangeValidation, - } - p := &Preflight{ - crdClient: crdCli, - // create a default validator. Can be overridden via the options - validator: &kappcus.Validator{ - Validations: []kappcus.Validation{ - kappcus.NewValidationFunc("NoScopeChange", kappcus.NoScopeChange), - kappcus.NewValidationFunc("NoStoredVersionRemoved", kappcus.NoStoredVersionRemoved), - kappcus.NewValidationFunc("NoExistingFieldRemoved", kappcus.NoExistingFieldRemoved), - &ServedVersionValidator{Validations: changeValidations}, - &kappcus.ChangeValidator{Validations: changeValidations}, - }, - }, - } - - for _, o := range opts { - o(p) - } - - return p -} - -func (p *Preflight) Install(ctx context.Context, rel *release.Release) error { - return p.runPreflight(ctx, rel) -} - -func (p *Preflight) Upgrade(ctx context.Context, rel *release.Release) error { - return p.runPreflight(ctx, rel) -} - -func (p *Preflight) runPreflight(ctx context.Context, rel *release.Release) error { - if rel == nil { - return nil - } - - relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) - if err != nil { - return fmt.Errorf("parsing release %q objects: %w", rel.Name, err) - } - - validateErrors := []error{} - for _, obj := range relObjects { - if obj.GetObjectKind().GroupVersionKind() != apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition") { - continue - } - - newCrd := &apiextensionsv1.CustomResourceDefinition{} - uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) - if err != nil { - return fmt.Errorf("converting object %q to unstructured: %w", obj.GetName(), err) - } - err = runtime.DefaultUnstructuredConverter.FromUnstructured(uMap, newCrd) - if err != nil { - return fmt.Errorf("converting unstructured to CRD object: %w", err) - } - - oldCrd, err := p.crdClient.Get(ctx, newCrd.Name, metav1.GetOptions{}) - if err != nil { - // if there is no existing CRD, there is nothing to break - // so it is immediately successful. - if apierrors.IsNotFound(err) { - continue - } - return fmt.Errorf("getting existing resource for CRD %q: %w", newCrd.Name, err) - } - - err = p.validator.Validate(*oldCrd, *newCrd) - if err != nil { - validateErrors = append(validateErrors, fmt.Errorf("validating upgrade for CRD %q failed: %w", newCrd.Name, err)) - } - } - - return errors.Join(validateErrors...) -} diff --git a/internal/rukpak/source/image_registry.go b/internal/rukpak/source/image_registry.go deleted file mode 100644 index 80233f7e64..0000000000 --- a/internal/rukpak/source/image_registry.go +++ /dev/null @@ -1,197 +0,0 @@ -package source - -import ( - "archive/tar" - "context" - "crypto/tls" - "errors" - "fmt" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/containerd/containerd/archive" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" - apimacherrors "k8s.io/apimachinery/pkg/util/errors" - "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/operator-framework/operator-controller/internal/httputil" -) - -// SourceTypeImage is the identifier for image-type bundle sources -const SourceTypeImage SourceType = "image" - -type ImageSource struct { - // Ref contains the reference to a container image containing Bundle contents. - Ref string - // InsecureSkipTLSVerify indicates that TLS certificate validation should be skipped. - // If this option is specified, the HTTPS protocol will still be used to - // fetch the specified image reference. - // This should not be used in a production environment. - InsecureSkipTLSVerify bool -} - -// Unrecoverable represents an error that can not be recovered -// from without user intervention. When this error is returned -// the request should not be requeued. -type Unrecoverable struct { - error -} - -func NewUnrecoverable(err error) *Unrecoverable { - return &Unrecoverable{err} -} - -// TODO: Make asynchronous - -type ImageRegistry struct { - BaseCachePath string - CertPoolWatcher *httputil.CertPoolWatcher -} - -func (i *ImageRegistry) Unpack(ctx context.Context, bundle *BundleSource) (*Result, error) { - l := log.FromContext(ctx) - if bundle.Type != SourceTypeImage { - panic(fmt.Sprintf("programmer error: source type %q is unable to handle specified bundle source type %q", SourceTypeImage, bundle.Type)) - } - - if bundle.Image == nil { - return nil, NewUnrecoverable(fmt.Errorf("error parsing bundle, bundle %s has a nil image source", bundle.Name)) - } - - imgRef, err := name.ParseReference(bundle.Image.Ref) - if err != nil { - return nil, NewUnrecoverable(fmt.Errorf("error parsing image reference: %w", err)) - } - - transport := remote.DefaultTransport.(*http.Transport).Clone() - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: false, - MinVersion: tls.VersionTLS12, - } // nolint:gosec - } - if bundle.Image.InsecureSkipTLSVerify { - transport.TLSClientConfig.InsecureSkipVerify = true // nolint:gosec - } - if i.CertPoolWatcher != nil { - pool, _, err := i.CertPoolWatcher.Get() - if err != nil { - return nil, err - } - transport.TLSClientConfig.RootCAs = pool - } - - remoteOpts := []remote.Option{} - remoteOpts = append(remoteOpts, remote.WithTransport(transport)) - - digest, isDigest := imgRef.(name.Digest) - if isDigest { - hexVal := strings.TrimPrefix(digest.DigestStr(), "sha256:") - unpackPath := filepath.Join(i.BaseCachePath, bundle.Name, hexVal) - if stat, err := os.Stat(unpackPath); err == nil && stat.IsDir() { - l.V(1).Info("found image in filesystem cache", "digest", hexVal) - return unpackedResult(os.DirFS(unpackPath), bundle, digest.String()), nil - } - } - - // always fetch the hash - imgDesc, err := remote.Head(imgRef, remoteOpts...) - if err != nil { - return nil, fmt.Errorf("error fetching image descriptor: %w", err) - } - l.V(1).Info("resolved image descriptor", "digest", imgDesc.Digest.String()) - - unpackPath := filepath.Join(i.BaseCachePath, bundle.Name, imgDesc.Digest.Hex) - if _, err = os.Stat(unpackPath); errors.Is(err, os.ErrNotExist) { //nolint: nestif - // Ensure any previous unpacked bundle is cleaned up before unpacking the new catalog. - if err := i.Cleanup(ctx, bundle); err != nil { - return nil, fmt.Errorf("error cleaning up bundle cache: %w", err) - } - - if err = os.MkdirAll(unpackPath, 0700); err != nil { - return nil, fmt.Errorf("error creating unpack path: %w", err) - } - - if err = unpackImage(ctx, imgRef, unpackPath, remoteOpts...); err != nil { - cleanupErr := os.RemoveAll(unpackPath) - if cleanupErr != nil { - err = apimacherrors.NewAggregate( - []error{ - err, - fmt.Errorf("error cleaning up unpack path after unpack failed: %w", cleanupErr), - }, - ) - } - return nil, wrapUnrecoverable(fmt.Errorf("error unpacking image: %w", err), isDigest) - } - } else if err != nil { - return nil, fmt.Errorf("error checking if image is in filesystem cache: %w", err) - } - - resolvedRef := fmt.Sprintf("%s@sha256:%s", imgRef.Context().Name(), imgDesc.Digest.Hex) - return unpackedResult(os.DirFS(unpackPath), bundle, resolvedRef), nil -} - -func wrapUnrecoverable(err error, isUnrecoverable bool) error { - if isUnrecoverable { - return NewUnrecoverable(err) - } - return err -} - -func (i *ImageRegistry) Cleanup(_ context.Context, bundle *BundleSource) error { - return os.RemoveAll(filepath.Join(i.BaseCachePath, bundle.Name)) -} - -func unpackedResult(fsys fs.FS, bundle *BundleSource, ref string) *Result { - return &Result{ - Bundle: fsys, - ResolvedSource: &BundleSource{ - Type: SourceTypeImage, - Image: &ImageSource{ - Ref: ref, - InsecureSkipTLSVerify: bundle.Image.InsecureSkipTLSVerify, - }, - }, - State: StateUnpacked, - } -} - -// unpackImage unpacks a bundle image reference to the provided unpackPath, -// returning an error if any errors are encountered along the way. -func unpackImage(ctx context.Context, imgRef name.Reference, unpackPath string, remoteOpts ...remote.Option) error { - img, err := remote.Image(imgRef, remoteOpts...) - if err != nil { - return fmt.Errorf("error fetching remote image %q: %w", imgRef.Name(), err) - } - - layers, err := img.Layers() - if err != nil { - return fmt.Errorf("error getting image layers: %w", err) - } - - for _, layer := range layers { - layerRc, err := layer.Uncompressed() - if err != nil { - return fmt.Errorf("error getting uncompressed layer data: %w", err) - } - - // This filter ensures that the files created have the proper UID and GID - // for the filesystem they will be stored on to ensure no permission errors occur when attempting to create the - // files. - _, err = archive.Apply(ctx, unpackPath, layerRc, archive.WithFilter(func(th *tar.Header) (bool, error) { - th.Uid = os.Getuid() - th.Gid = os.Getgid() - return true, nil - })) - if err != nil { - return fmt.Errorf("error applying layer to archive: %w", err) - } - } - - return nil -} diff --git a/internal/rukpak/source/image_registry_test.go b/internal/rukpak/source/image_registry_test.go deleted file mode 100644 index 4cbd1d05b8..0000000000 --- a/internal/rukpak/source/image_registry_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package source_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "testing" - - "github.com/google/go-containerregistry/pkg/crane" - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/operator-framework/operator-controller/internal/rukpak/source" -) - -const ( - testFilePathBase string = ".image-registry-test" - bogusDigestHex string = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - testImageTag string = "test-tag" - testImageName string = "test-image" - badImageName string = "bad-image" - testFileName string = "test-file" - testFileContents string = "test-content" -) - -func newReference(host, repo, ref string) (name.Reference, error) { - tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", host, repo, ref), name.WeakValidation) - if err == nil { - return tag, nil - } - return name.NewDigest(fmt.Sprintf("%s/%s@%s", host, repo, ref), name.WeakValidation) -} - -func imageToRawManifest(img v1.Image) ([]byte, error) { - manifest, err := img.Manifest() - if err != nil { - return nil, err - } - - layers, err := img.Layers() - if err != nil { - return nil, err - } - - rc, err := layers[0].Compressed() - if err != nil { - return nil, err - } - - lb, err := io.ReadAll(rc) - if err != nil { - return nil, err - } - - manifest.Layers[0].Data = lb - rawManifest, err := json.Marshal(manifest) - if err != nil { - return nil, err - } - - return rawManifest, nil -} - -// Adapted from: https://github.com/google/go-containerregistry/blob/main/pkg/v1/remote/image_test.go -// serveImageManifest starts a primitive image registry server hosting two images: "test-image" and "bad-image". -func serveImageManifest(rawManifest []byte) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/v2/": - w.WriteHeader(http.StatusOK) - case fmt.Sprintf("/v2/%s/manifests/%s", testImageName, testImageTag): - if r.Method == http.MethodHead { - w.Header().Set("Content-Length", fmt.Sprint(len(rawManifest))) - w.Header().Set("Docker-Content-Digest", fmt.Sprintf("sha256:%s", bogusDigestHex)) - w.WriteHeader(http.StatusOK) - } else if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - } - _, err := w.Write(rawManifest) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - case fmt.Sprintf("/v2/%s/manifests/%s", badImageName, testImageTag): - case fmt.Sprintf("/v2/%s/manifests/sha256:%s", badImageName, bogusDigestHex): - if r.Method == http.MethodHead { - w.Header().Set("Content-Length", fmt.Sprint(len(rawManifest))) - w.Header().Set("Docker-Content-Digest", fmt.Sprintf("sha256:%s", bogusDigestHex)) - // We must set Content-Type since we're returning empty data below - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - return - } else if r.Method != http.MethodGet { - w.WriteHeader(http.StatusBadRequest) - } - _, err := w.Write(make([]byte, 0)) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - default: - w.WriteHeader(http.StatusBadRequest) - } - })) -} - -func testFileCleanup() { - if _, err := os.Stat(testFilePathBase); err != nil && errors.Is(err, os.ErrNotExist) { - // Nothing to clean up - return - } else if err != nil { - log.Fatalf("error occurred locating unpack folder in post-test cleanup: %v", err) - } - // Ensure permissions and remove the temporary directory - err := os.Chmod(testFilePathBase, os.ModePerm) - if err != nil { - log.Fatalf("error occurred ensuring unpack folder permissions in post-test cleanup: %v", err) - } - err = os.RemoveAll(testFilePathBase) - if err != nil { - log.Fatalf("error occurred deleting unpack folder in post-test cleanup: %v", err) - } -} - -var HostedImageReference name.Reference - -func TestMain(m *testing.M) { - // Generate an image with file contents - img, err := crane.Image(map[string][]byte{testFileName: []byte(testFileContents)}) - if err != nil { - log.Fatalf("failed to generate image for test") - } - // Create a raw bytes manifest from the image - rawManifest, err := imageToRawManifest(img) - if err != nil { - log.Fatalf("failed to generate manifest from image") - } - - // Start the image registry and serve the generated image manifest - server := serveImageManifest(rawManifest) - if err != nil { - log.Fatalf("image registry server failed to start") - } - u, err := url.Parse(server.URL) - if err != nil { - log.Fatalf("invalid server URL from image registry") - } - HostedImageReference, err = newReference(u.Host, testImageName, testImageTag) - if err != nil { - log.Fatalf("failed to generate image reference for served image") - } - code := m.Run() - server.Close() - os.Exit(code) -} - -func TestUnpackValidInsecure(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: HostedImageReference.String(), - InsecureSkipTLSVerify: true, - }, - } - - unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex) - - // Create another folder to simulate an old unpacked bundle - oldBundlePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, "foo") - require.NoError(t, os.MkdirAll(oldBundlePath, os.ModePerm)) - - // Attempt to pull and unpack the image - result, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.NoError(t, err) - require.NotNil(t, result) - assert.Equal(t, result.State, source.StateUnpacked) - // Make sure the old bundle was cleaned up - require.NoDirExists(t, oldBundlePath) - - // Give permissions to read the file - require.NoError(t, os.Chmod(filepath.Join(unpackPath, testFileName), 0400)) - unpackedFile, err := os.ReadFile(filepath.Join(unpackPath, testFileName)) - require.NoError(t, err) - // Ensure the unpacked file matches the source content - require.Equal(t, []byte(testFileContents), unpackedFile) -} - -func TestUnpackValidUsesCache(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: fmt.Sprintf("%s@sha256:%s", HostedImageReference.Context().Name(), bogusDigestHex), - InsecureSkipTLSVerify: true, - }, - } - - // Populate the bundle cache with a folder - testCacheFilePath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex, "test-folder") - require.NoError(t, os.MkdirAll(testCacheFilePath, os.ModePerm)) - - // Attempt to pull and unpack the image - result, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.NoError(t, err) - require.NotNil(t, result) - assert.Equal(t, result.State, source.StateUnpacked) - // Make sure the old file was not cleaned up - require.DirExists(t, testCacheFilePath) -} - -func TestUnpackCacheCheckError(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: HostedImageReference.String(), - InsecureSkipTLSVerify: true, - }, - } - - // Create the unpack path and restrict its permissions - unpackPath := filepath.Join(unpacker.BaseCachePath, bundleSource.Name, bogusDigestHex) - require.NoError(t, os.MkdirAll(unpackPath, os.ModePerm)) - require.NoError(t, os.Chmod(testFilePathBase, 0000)) - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackUnservedImageReference(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is not served - Ref: fmt.Sprintf("%s/%s:unserved-tag", HostedImageReference.Context().Registry.RegistryStr(), badImageName), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackFailure(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is served but will return bad image content - Ref: fmt.Sprintf("%s/%s:%s", HostedImageReference.Context().Registry.RegistryStr(), badImageName, testImageTag), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) -} - -func TestUnpackFailureDigest(t *testing.T) { - defer testFileCleanup() - unpacker := &source.ImageRegistry{ - BaseCachePath: testFilePathBase, - } - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - // Use a valid reference that is served but will return bad image content - Ref: fmt.Sprintf("%s/%s@sha256:%s", HostedImageReference.Context().Registry.RegistryStr(), badImageName, bogusDigestHex), - InsecureSkipTLSVerify: true, - }, - } - - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - // Check Result - require.Error(t, err) - // Unpacker gives an error of type Unrecoverable - require.IsType(t, &source.Unrecoverable{}, err) -} - -func TestUnpackInvalidSourceType(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with invalid source type - bundleSource := &source.BundleSource{ - Type: "invalid", - } - - shouldPanic := func() { - // Attempt to pull and unpack the image - _, err := unpacker.Unpack(context.Background(), bundleSource) - if err != nil { - t.Error("func should have panicked") - } - } - require.Panics(t, shouldPanic) -} - -func TestUnpackInvalidNilImage(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with nil Image - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: nil, - } - - // Attempt to unpack - result, err := unpacker.Unpack(context.Background(), bundleSource) - require.Error(t, err) - require.NoDirExists(t, testFilePathBase) - assert.Nil(t, result) -} - -func TestUnpackInvalidImageRef(t *testing.T) { - unpacker := &source.ImageRegistry{} - // Create BundleSource with malformed image reference - bundleSource := &source.BundleSource{ - Type: source.SourceTypeImage, - Image: &source.ImageSource{ - Ref: "invalid image ref", - }, - } - - // Attempt to unpack - result, err := unpacker.Unpack(context.Background(), bundleSource) - require.Error(t, err) - require.NoDirExists(t, testFilePathBase) - assert.Nil(t, result) -} diff --git a/internal/rukpak/source/unpacker.go b/internal/rukpak/source/unpacker.go deleted file mode 100644 index e98b92a68e..0000000000 --- a/internal/rukpak/source/unpacker.go +++ /dev/null @@ -1,102 +0,0 @@ -package source - -import ( - "context" - "fmt" - "io/fs" -) - -// Unpacker unpacks bundle content, either synchronously or asynchronously and -// returns a Result, which conveys information about the progress of unpacking -// the bundle content. -// -// If a Source unpacks content asynchronously, it should register one or more -// watches with a controller to ensure that Bundles referencing this source -// can be reconciled as progress updates are available. -// -// For asynchronous Sources, multiple calls to Unpack should be made until the -// returned result includes state StateUnpacked. -// -// NOTE: A source is meant to be agnostic to specific bundle formats and -// specifications. A source should treat a bundle root directory as an opaque -// file tree and delegate bundle format concerns to bundle parsers. -type Unpacker interface { - Unpack(context.Context, *BundleSource) (*Result, error) - Cleanup(context.Context, *BundleSource) error -} - -// Result conveys progress information about unpacking bundle content. -type Result struct { - // Bundle contains the full filesystem of a bundle's root directory. - Bundle fs.FS - - // ResolvedSource is a reproducible view of a Bundle's Source. - // When possible, source implementations should return a ResolvedSource - // that pins the Source such that future fetches of the bundle content can - // be guaranteed to fetch the exact same bundle content as the original - // unpack. - // - // For example, resolved image sources should reference a container image - // digest rather than an image tag, and git sources should reference a - // commit hash rather than a branch or tag. - ResolvedSource *BundleSource - - // State is the current state of unpacking the bundle content. - State State - - // Message is contextual information about the progress of unpacking the - // bundle content. - Message string -} - -type State string - -const ( - // StatePending conveys that a request for unpacking a bundle has been - // acknowledged, but not yet started. - StatePending State = "Pending" - - // StateUnpacking conveys that the source is currently unpacking a bundle. - // This state should be used when the bundle contents are being downloaded - // and processed. - StateUnpacking State = "Unpacking" - - // StateUnpacked conveys that the bundle has been successfully unpacked. - StateUnpacked State = "Unpacked" -) - -type SourceType string - -type BundleSource struct { - Name string - // Type defines the kind of Bundle content being sourced. - Type SourceType - // Image is the bundle image that backs the content of this bundle. - Image *ImageSource -} - -type unpacker struct { - sources map[SourceType]Unpacker -} - -// NewUnpacker returns a new composite Source that unpacks bundles using the source -// mapping provided by the configured sources. -func NewUnpacker(sources map[SourceType]Unpacker) Unpacker { - return &unpacker{sources: sources} -} - -func (s *unpacker) Unpack(ctx context.Context, bundle *BundleSource) (*Result, error) { - source, ok := s.sources[bundle.Type] - if !ok { - return nil, fmt.Errorf("source type %q not supported", bundle.Type) - } - return source.Unpack(ctx, bundle) -} - -func (s *unpacker) Cleanup(ctx context.Context, bundle *BundleSource) error { - source, ok := s.sources[bundle.Type] - if !ok { - return fmt.Errorf("source type %q not supported", bundle.Type) - } - return source.Cleanup(ctx, bundle) -} diff --git a/internal/rukpak/util/util.go b/internal/rukpak/util/util.go deleted file mode 100644 index 8bcfa194ed..0000000000 --- a/internal/rukpak/util/util.go +++ /dev/null @@ -1,39 +0,0 @@ -package util - -import ( - "io" - - "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func MergeMaps(maps ...map[string]string) map[string]string { - out := map[string]string{} - for _, m := range maps { - for k, v := range m { - out[k] = v - } - } - return out -} - -func ManifestObjects(r io.Reader, name string) ([]client.Object, error) { - result := resource.NewLocalBuilder().Flatten().Unstructured().Stream(r, name).Do() - if err := result.Err(); err != nil { - return nil, err - } - infos, err := result.Infos() - if err != nil { - return nil, err - } - return infosToObjects(infos), nil -} - -func infosToObjects(infos []*resource.Info) []client.Object { - objects := make([]client.Object, 0, len(infos)) - for _, info := range infos { - clientObject := info.Object.(client.Object) - objects = append(objects, clientObject) - } - return objects -} diff --git a/internal/shared/controllers/pull_secret_controller.go b/internal/shared/controllers/pull_secret_controller.go new file mode 100644 index 0000000000..43a2ceec80 --- /dev/null +++ b/internal/shared/controllers/pull_secret_controller.go @@ -0,0 +1,243 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/go-logr/logr" + "github.com/google/renameio/v2" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// PullSecretReconciler reconciles a specific Secret object +// that contains global pull secrets for pulling Catalog images +type PullSecretReconciler struct { + client.Client + SecretKey *types.NamespacedName + ServiceAccountKey types.NamespacedName + ServiceAccountPullSecrets []types.NamespacedName + AuthFilePath string +} + +func (r *PullSecretReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx).WithName("pull-secret-reconciler") + + logger.Info("starting reconciliation") + defer logger.Info("finishing reconciliation") + + secrets := []*corev1.Secret{} + + if r.SecretKey != nil { + secret, err := r.getSecret(ctx, logger, *r.SecretKey) + if err != nil { + return ctrl.Result{}, err + } + // Add the configured pull secret to the list of secrets + if secret != nil { + secrets = append(secrets, secret) + } + } + + // Grab all the pull secrets from the serviceaccount and add them to the list of secrets + sa := &corev1.ServiceAccount{} + if err := r.Get(ctx, r.ServiceAccountKey, sa); err != nil { //nolint:nestif + if apierrors.IsNotFound(err) { + logger.Info("serviceaccount not found", "pod-sa", logNamespacedName(r.ServiceAccountKey)) + } else { + logger.Error(err, "failed to get serviceaccount", "pod-sa", logNamespacedName(r.ServiceAccountKey)) + return ctrl.Result{}, err + } + } else { + logger.Info("found serviceaccount", "pod-sa", logNamespacedName(r.ServiceAccountKey)) + nn := types.NamespacedName{Namespace: r.ServiceAccountKey.Namespace} + pullSecrets := []types.NamespacedName{} + for _, ips := range sa.ImagePullSecrets { + nn.Name = ips.Name + // This is to update the list of secrets that we are filtering on + // Add all secrets regardless if they exist or not + pullSecrets = append(pullSecrets, nn) + + secret, err := r.getSecret(ctx, logger, nn) + if err != nil { + return ctrl.Result{}, err + } + if secret != nil { + secrets = append(secrets, secret) + } + } + // update list of pull secrets from service account + r.ServiceAccountPullSecrets = pullSecrets + // Log ever-so slightly nicer + names := []string{} + for _, ps := range pullSecrets { + names = append(names, logNamespacedName(ps)) + } + logger.Info("updating list of pull-secrets", "pull-secrets", names) + } + + if len(secrets) == 0 { + return ctrl.Result{}, r.deleteSecretFile(logger) + } + return ctrl.Result{}, r.writeSecretToFile(logger, secrets) +} + +func (r *PullSecretReconciler) getSecret(ctx context.Context, logger logr.Logger, nn types.NamespacedName) (*corev1.Secret, error) { + secret := &corev1.Secret{} + if err := r.Get(ctx, nn, secret); err != nil { + if apierrors.IsNotFound(err) { + logger.Info("pull-secret not found", "pull-secret", logNamespacedName(nn)) + return nil, nil + } + logger.Error(err, "failed to get pull-secret", "pull-secret", logNamespacedName(nn)) + return nil, err + } + logger.Info("found pull-secret", "pull-secret", logNamespacedName(nn)) + return secret, nil +} + +func logNamespacedName(nn types.NamespacedName) string { + return fmt.Sprintf("%s/%s", nn.Namespace, nn.Name) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PullSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { + _, err := ctrl.NewControllerManagedBy(mgr). + For(&corev1.Secret{}). + Named("pull-secret-controller"). + WithEventFilter(newSecretPredicate(r)). + Build(r) + if err != nil { + return err + } + + _, err = ctrl.NewControllerManagedBy(mgr). + For(&corev1.ServiceAccount{}). + Named("service-account-controller"). + WithEventFilter(newNamespacedPredicate(r.ServiceAccountKey)). + Build(r) + + return err +} + +// Filters based on the global SecretKey, or any pull secret from the serviceaccount +func newSecretPredicate(r *PullSecretReconciler) predicate.Predicate { + return predicate.NewPredicateFuncs(func(obj client.Object) bool { + nn := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + if r.SecretKey != nil && nn == *r.SecretKey { + return true + } + for _, ps := range r.ServiceAccountPullSecrets { + if nn == ps { + return true + } + } + return false + }) +} + +func newNamespacedPredicate(key types.NamespacedName) predicate.Predicate { + return predicate.NewPredicateFuncs(func(obj client.Object) bool { + return obj.GetName() == key.Name && obj.GetNamespace() == key.Namespace + }) +} + +// Golang representation of the docker configuration - either dockerconfigjson or dockercfg formats. +// This allows us to merge the two formats together, regardless of type, and dump it out as a +// dockerconfigjson for use my contaners/images +type dockerConfigJSON struct { + Auths dockerCfg `json:"auths"` +} + +type dockerCfg map[string]authEntries + +type authEntries struct { + Auth string `json:"auth"` + Email string `json:"email,omitempty"` +} + +// writeSecretToFile writes the secret data to the specified file +func (r *PullSecretReconciler) writeSecretToFile(logger logr.Logger, secrets []*corev1.Secret) error { + // image registry secrets are always stored with the key .dockerconfigjson or .dockercfg + // ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials + // expected format for auth.json + // ref: https://github.com/containers/image/blob/main/docs/containers-auth.json.5.md + + jsonData := dockerConfigJSON{} + jsonData.Auths = make(dockerCfg) + + for _, s := range secrets { + if secretData, ok := s.Data[".dockerconfigjson"]; ok { + // process as dockerconfigjson + dcj := &dockerConfigJSON{} + if err := json.Unmarshal(secretData, dcj); err != nil { + return err + } + for n, v := range dcj.Auths { + jsonData.Auths[n] = v + } + continue + } + if secretData, ok := s.Data[".dockercfg"]; ok { + // process as dockercfg, despite being a map, this has to be Unmarshal'd as a pointer + dc := &dockerCfg{} + if err := json.Unmarshal(secretData, dc); err != nil { + return err + } + for n, v := range *dc { + jsonData.Auths[n] = v + } + continue + } + // Ignore the unknown secret + logger.Info("expected secret.Data key not found", "pull-secret", logNamespacedName(types.NamespacedName{Name: s.Name, Namespace: s.Namespace})) + } + + data, err := json.Marshal(jsonData) + if err != nil { + return fmt.Errorf("failed to marshal secret data: %w", err) + } + err = renameio.WriteFile(r.AuthFilePath, data, 0600) + if err != nil { + return fmt.Errorf("failed to write secret data to file: %w", err) + } + logger.Info("saved global pull secret data locally") + return nil +} + +// deleteSecretFile deletes the auth file if the secret is deleted +func (r *PullSecretReconciler) deleteSecretFile(logger logr.Logger) error { + logger.Info("deleting local auth file", "file", r.AuthFilePath) + if err := os.Remove(r.AuthFilePath); err != nil { + if os.IsNotExist(err) { + logger.Info("auth file does not exist, nothing to delete") + return nil + } + return fmt.Errorf("failed to delete secret file: %w", err) + } + logger.Info("auth file deleted successfully") + return nil +} diff --git a/internal/shared/controllers/pull_secret_controller_test.go b/internal/shared/controllers/pull_secret_controller_test.go new file mode 100644 index 0000000000..1619267551 --- /dev/null +++ b/internal/shared/controllers/pull_secret_controller_test.go @@ -0,0 +1,154 @@ +package controllers + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestSecretSyncerReconciler(t *testing.T) { + secretFullData := []byte(`{"auths":{"exampleRegistry": {"auth": "exampledata"}}}`) + secretPartData := []byte(`{"exampleRegistry": {"auth": "exampledata"}}`) + authFileName := "test-auth.json" + for _, tt := range []struct { + name string + secretKey *types.NamespacedName + sa *corev1.ServiceAccount + secrets []corev1.Secret + wantErr string + fileShouldExistBefore bool + fileShouldExistAfter bool + }{ + { + name: "secret exists, dockerconfigjson content gets saved to authFile", + secretKey: &types.NamespacedName{Namespace: "test-secret-namespace", Name: "test-secret"}, + secrets: []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "test-secret-namespace", + }, + Data: map[string][]byte{ + ".dockerconfigjson": secretFullData, + }, + }, + }, + fileShouldExistBefore: false, + fileShouldExistAfter: true, + }, + { + name: "secret exists, dockercfg content gets saved to authFile", + secretKey: &types.NamespacedName{Namespace: "test-secret-namespace", Name: "test-secret"}, + secrets: []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "test-secret-namespace", + }, + Data: map[string][]byte{ + ".dockercfg": secretPartData, + }, + }, + }, + fileShouldExistBefore: false, + fileShouldExistAfter: true, + }, + { + name: "secret does not exist, file exists previously, file should get deleted", + secretKey: &types.NamespacedName{Namespace: "test-secret-namespace", Name: "test-secret"}, + fileShouldExistBefore: true, + fileShouldExistAfter: false, + }, + { + name: "serviceaccount secrets, both dockerconfigjson and dockercfg content gets saved to authFile", + sa: &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sa", + Namespace: "test-secret-namespace", + }, + ImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "test-secret1"}, + {Name: "test-secret2"}, + }, + }, + secrets: []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret1", + Namespace: "test-secret-namespace", + }, + Data: map[string][]byte{ + ".dockerconfigjson": secretFullData, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret2", + Namespace: "test-secret-namespace", + }, + Data: map[string][]byte{ + ".dockerconfigjson": secretFullData, + }, + }, + }, + fileShouldExistBefore: false, + fileShouldExistAfter: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + tempAuthFile := filepath.Join(t.TempDir(), authFileName) + clientBuilder := fake.NewClientBuilder() + for _, ps := range tt.secrets { + clientBuilder = clientBuilder.WithObjects(ps.DeepCopy()) + } + if tt.sa != nil { + clientBuilder = clientBuilder.WithObjects(tt.sa) + } + cl := clientBuilder.Build() + + var triggerKey types.NamespacedName + if tt.secretKey != nil { + triggerKey = *tt.secretKey + } + var saKey types.NamespacedName + if tt.sa != nil { + saKey = types.NamespacedName{Namespace: tt.sa.Namespace, Name: tt.sa.Name} + triggerKey = saKey + } + r := &PullSecretReconciler{ + Client: cl, + SecretKey: tt.secretKey, + ServiceAccountKey: saKey, + AuthFilePath: tempAuthFile, + } + if tt.fileShouldExistBefore { + err := os.WriteFile(tempAuthFile, secretFullData, 0600) + require.NoError(t, err) + } + res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: triggerKey}) + if tt.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + require.Equal(t, ctrl.Result{}, res) + + if tt.fileShouldExistAfter { + _, err := os.Stat(tempAuthFile) + require.NoError(t, err) + } else { + _, err := os.Stat(tempAuthFile) + require.True(t, os.IsNotExist(err)) + } + }) + } +} diff --git a/internal/shared/util/error/terminal.go b/internal/shared/util/error/terminal.go new file mode 100644 index 0000000000..cd70d535f7 --- /dev/null +++ b/internal/shared/util/error/terminal.go @@ -0,0 +1,10 @@ +package error + +import "sigs.k8s.io/controller-runtime/pkg/reconcile" + +func WrapTerminal(err error, isTerminal bool) error { + if !isTerminal || err == nil { + return err + } + return reconcile.TerminalError(err) +} diff --git a/internal/shared/util/featuregates/logging.go b/internal/shared/util/featuregates/logging.go new file mode 100644 index 0000000000..6649a350da --- /dev/null +++ b/internal/shared/util/featuregates/logging.go @@ -0,0 +1,29 @@ +package featuregates + +import ( + "sort" + + "github.com/go-logr/logr" + "k8s.io/component-base/featuregate" +) + +// LogFeatureGateStates logs a sorted list of features and their enabled state +// message is the log message under which to record the feature states +// fg is the feature gate instance, and featureDefs is the map of feature specs +func LogFeatureGateStates(log logr.Logger, message string, fg featuregate.FeatureGate, featureDefs map[featuregate.Feature]featuregate.FeatureSpec) { + // Collect and sort feature keys for deterministic ordering + keys := make([]featuregate.Feature, 0, len(featureDefs)) + for f := range featureDefs { + keys = append(keys, f) + } + sort.Slice(keys, func(i, j int) bool { + return string(keys[i]) < string(keys[j]) + }) + + // Build key/value pairs for logging + pairs := make([]interface{}, 0, len(keys)*2) + for _, f := range keys { + pairs = append(pairs, f, fg.Enabled(f)) + } + log.Info(message, pairs...) +} diff --git a/internal/shared/util/featuregates/logging_test.go b/internal/shared/util/featuregates/logging_test.go new file mode 100644 index 0000000000..1d4b163e3a --- /dev/null +++ b/internal/shared/util/featuregates/logging_test.go @@ -0,0 +1,78 @@ +package featuregates_test + +import ( + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/require" + "k8s.io/component-base/featuregate" + + "github.com/operator-framework/operator-controller/internal/shared/util/featuregates" +) + +// fakeSink implements logr.LogSink, capturing Info calls for testing +type fakeSink struct { + level int + msg string + keysAndValues []interface{} +} + +// Init is part of logr.LogSink +func (f *fakeSink) Init(info logr.RuntimeInfo) {} + +// Enabled is part of logr.LogSink +func (f *fakeSink) Enabled(level int) bool { return true } + +// Info captures the log level, message, and key/value pairs +func (f *fakeSink) Info(level int, msg string, keysAndValues ...interface{}) { + f.level = level + f.msg = msg + f.keysAndValues = append([]interface{}{}, keysAndValues...) +} + +// Error is part of logr.LogSink; not used in this test +func (f *fakeSink) Error(err error, msg string, keysAndValues ...interface{}) {} + +// WithValues returns a sink with additional values; for testing, return self +func (f *fakeSink) WithValues(keysAndValues ...interface{}) logr.LogSink { return f } + +// WithName returns a sink with a new name; for testing, return self +func (f *fakeSink) WithName(name string) logr.LogSink { return f } + +// TestLogFeatureGateStates verifies that LogFeatureGateStates logs features +// sorted alphabetically with their enabled state +func TestLogFeatureGateStates(t *testing.T) { + // Define a set of feature specs with default states + defs := map[featuregate.Feature]featuregate.FeatureSpec{ + "AFeature": {Default: false}, + "BFeature": {Default: true}, + "CFeature": {Default: false}, + } + + // create a mutable gate and register our definitions + gate := featuregate.NewFeatureGate() + require.NoError(t, gate.Add(defs)) + + // override CFeature to true. + require.NoError(t, gate.SetFromMap(map[string]bool{ + "CFeature": true, + })) + + // prepare a fake sink and logger + sink := &fakeSink{} + logger := logr.New(sink) + + // log the feature states + featuregates.LogFeatureGateStates(logger, "feature states", gate, defs) + + // verify the message + require.Equal(t, "feature states", sink.msg) + + // Expect keys sorted: AFeature, BFeature, CFeature + want := []interface{}{ + featuregate.Feature("AFeature"), false, + featuregate.Feature("BFeature"), true, + featuregate.Feature("CFeature"), true, + } + require.Equal(t, want, sink.keysAndValues) +} diff --git a/internal/catalogmetadata/filter/filter.go b/internal/shared/util/filter/filter.go similarity index 52% rename from internal/catalogmetadata/filter/filter.go rename to internal/shared/util/filter/filter.go index 9074c926d9..96d60cfcd6 100644 --- a/internal/catalogmetadata/filter/filter.go +++ b/internal/shared/util/filter/filter.go @@ -1,17 +1,10 @@ package filter -import ( - "slices" -) +import "slices" // Predicate returns true if the object should be kept when filtering type Predicate[T any] func(entity T) bool -// Filter filters a slice accordingly to -func Filter[T any](in []T, test Predicate[T]) []T { - return slices.DeleteFunc(in, Not(test)) -} - func And[T any](predicates ...Predicate[T]) Predicate[T] { return func(obj T) bool { for _, predicate := range predicates { @@ -39,3 +32,21 @@ func Not[T any](predicate Predicate[T]) Predicate[T] { return !predicate(obj) } } + +// Filter creates a new slice with all elements from s for which the test returns true +func Filter[T any](s []T, test Predicate[T]) []T { + var out []T + for i := 0; i < len(s); i++ { + if test(s[i]) { + out = append(out, s[i]) + } + } + return slices.Clip(out) +} + +// InPlace modifies s by removing any element for which test returns false. +// InPlace zeroes the elements between the new length and the original length in s. +// The returned slice is of the new length. +func InPlace[T any](s []T, test Predicate[T]) []T { + return slices.DeleteFunc(s, Not(test)) +} diff --git a/internal/shared/util/filter/filter_test.go b/internal/shared/util/filter/filter_test.go new file mode 100644 index 0000000000..46f4d58127 --- /dev/null +++ b/internal/shared/util/filter/filter_test.go @@ -0,0 +1,239 @@ +package filter_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/operator-framework/operator-controller/internal/shared/util/filter" +) + +func TestAnd(t *testing.T) { + tests := []struct { + name string + predicates []filter.Predicate[int] + input int + want bool + }{ + { + name: "all true", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 0 }, + func(i int) bool { return i < 10 }, + }, + input: 5, + want: true, + }, + { + name: "one false", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 0 }, + func(i int) bool { return i < 5 }, + }, + input: 5, + want: false, + }, + { + name: "all false", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 10 }, + func(i int) bool { return i < 0 }, + }, + input: 5, + want: false, + }, + { + name: "no predicates", + predicates: []filter.Predicate[int]{}, + input: 5, + want: true, + }, + { + name: "nil predicates", + predicates: nil, + input: 5, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := filter.And(tt.predicates...)(tt.input) + require.Equal(t, tt.want, got, "And() = %v, want %v", got, tt.want) + }) + } +} + +func TestOr(t *testing.T) { + tests := []struct { + name string + predicates []filter.Predicate[int] + input int + want bool + }{ + { + name: "all true", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 0 }, + func(i int) bool { return i < 10 }, + }, + input: 5, + want: true, + }, + { + name: "one false", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 0 }, + func(i int) bool { return i < 5 }, + }, + input: 5, + want: true, + }, + { + name: "all false", + predicates: []filter.Predicate[int]{ + func(i int) bool { return i > 10 }, + func(i int) bool { return i < 0 }, + }, + input: 5, + want: false, + }, + { + name: "no predicates", + predicates: []filter.Predicate[int]{}, + input: 5, + want: false, + }, + { + name: "nil predicates", + predicates: nil, + input: 5, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := filter.Or(tt.predicates...)(tt.input) + require.Equal(t, tt.want, got, "Or() = %v, want %v", got, tt.want) + }) + } +} + +func TestNot(t *testing.T) { + tests := []struct { + name string + predicate filter.Predicate[int] + input int + want bool + }{ + { + name: "predicate is true", + predicate: func(i int) bool { return i > 0 }, + input: 5, + want: false, + }, + { + name: "predicate is false", + predicate: func(i int) bool { return i > 3 }, + input: 2, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := filter.Not(tt.predicate)(tt.input) + require.Equal(t, tt.want, got, "Not() = %v, want %v", got, tt.want) + }) + } +} + +func TestFilter(t *testing.T) { + tests := []struct { + name string + slice []int + predicate filter.Predicate[int] + want []int + }{ + { + name: "all match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 0 }, + want: []int{1, 2, 3, 4, 5}, + }, + { + name: "some match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 3 }, + want: []int{4, 5}, + }, + { + name: "none match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 5 }, + want: nil, + }, + { + name: "empty slice", + slice: []int{}, + predicate: func(i int) bool { return i > 5 }, + want: nil, + }, + { + name: "nil slice", + slice: nil, + predicate: func(i int) bool { return i > 5 }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := filter.Filter(tt.slice, tt.predicate) + require.Equal(t, tt.want, got, "Filter() = %v, want %v", got, tt.want) + }) + } +} + +func TestInPlace(t *testing.T) { + tests := []struct { + name string + slice []int + predicate filter.Predicate[int] + want []int + }{ + { + name: "all match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 0 }, + want: []int{1, 2, 3, 4, 5}, + }, + { + name: "some match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 3 }, + want: []int{4, 5}, + }, + { + name: "none match", + slice: []int{1, 2, 3, 4, 5}, + predicate: func(i int) bool { return i > 5 }, + want: []int{}, + }, + { + name: "empty slice", + slice: []int{}, + predicate: func(i int) bool { return i > 5 }, + want: []int{}, + }, + { + name: "nil slice", + slice: nil, + predicate: func(i int) bool { return i > 5 }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := filter.InPlace(tt.slice, tt.predicate) + require.Equal(t, tt.want, got, "Filter() = %v, want %v", got, tt.want) + }) + } +} diff --git a/internal/shared/util/fs/fs.go b/internal/shared/util/fs/fs.go new file mode 100644 index 0000000000..f6ba9ec577 --- /dev/null +++ b/internal/shared/util/fs/fs.go @@ -0,0 +1,103 @@ +package fs + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "time" +) + +// EnsureEmptyDirectory ensures the directory given by `path` is empty. +// If the directory does not exist, it will be created with permission bits +// given by `perm`. If the directory exists, it will not simply rm -rf && mkdir -p +// as the calling process may not have permissions to delete the directory. E.g. +// in the case of a pod mount. Rather, it will delete the contents of the directory. +func EnsureEmptyDirectory(path string, perm fs.FileMode) error { + entries, err := os.ReadDir(path) + if err != nil && !os.IsNotExist(err) { + return err + } + for _, entry := range entries { + if err := DeleteReadOnlyRecursive(filepath.Join(path, entry.Name())); err != nil { + return err + } + } + return os.MkdirAll(path, perm) +} + +const ( + ownerWritableFileMode os.FileMode = 0700 + ownerWritableDirMode os.FileMode = 0700 + ownerReadOnlyFileMode os.FileMode = 0400 + ownerReadOnlyDirMode os.FileMode = 0500 +) + +// SetReadOnlyRecursive recursively sets files and directories under the path given by `root` as read-only +func SetReadOnlyRecursive(root string) error { + return setModeRecursive(root, ownerReadOnlyFileMode, ownerReadOnlyDirMode) +} + +// SetWritableRecursive recursively sets files and directories under the path given by `root` as writable +func SetWritableRecursive(root string) error { + return setModeRecursive(root, ownerWritableFileMode, ownerWritableDirMode) +} + +// DeleteReadOnlyRecursive deletes the directory with path given by `root`. +// Prior to deleting the directory, the directory and all descendant files +// and directories are set as writable. If any chmod or deletion error occurs +// it is immediately returned. +func DeleteReadOnlyRecursive(root string) error { + if err := SetWritableRecursive(root); err != nil { + return fmt.Errorf("error making directory writable for deletion: %w", err) + } + return os.RemoveAll(root) +} + +func setModeRecursive(path string, fileMode os.FileMode, dirMode os.FileMode) error { + return filepath.WalkDir(path, func(path string, d os.DirEntry, err error) error { + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + fi, err := d.Info() + if err != nil { + return err + } + + switch typ := fi.Mode().Type(); typ { + case os.ModeSymlink: + // do not follow symlinks + // 1. if they resolve to other locations in the root, we'll find them anyway + // 2. if they resolve to other locations outside the root, we don't want to change their permissions + return nil + case os.ModeDir: + return os.Chmod(path, dirMode) + case 0: // regular file + return os.Chmod(path, fileMode) + default: + return fmt.Errorf("refusing to change ownership of file %q with type %v", path, typ.String()) + } + }) +} + +var ( + ErrNotDirectory = errors.New("not a directory") +) + +// GetDirectoryModTime returns the modification time of the directory at dirPath. +// If stat(dirPath) fails, an error is returned with a zero-value time.Time. +// If dirPath is not a directory, an ErrNotDirectory error is returned. +func GetDirectoryModTime(dirPath string) (time.Time, error) { + dirStat, err := os.Stat(dirPath) + if err != nil { + return time.Time{}, err + } + if !dirStat.IsDir() { + return time.Time{}, ErrNotDirectory + } + return dirStat.ModTime(), nil +} diff --git a/internal/shared/util/fs/fs_test.go b/internal/shared/util/fs/fs_test.go new file mode 100644 index 0000000000..00579a51f4 --- /dev/null +++ b/internal/shared/util/fs/fs_test.go @@ -0,0 +1,174 @@ +package fs + +import ( + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEnsureEmptyDirectory(t *testing.T) { + tempDir := t.TempDir() + dirPath := filepath.Join(tempDir, "testdir") + dirPerms := os.FileMode(0755) + + t.Log("Ensure directory is created with the correct perms if it does not already exist") + require.NoError(t, EnsureEmptyDirectory(dirPath, dirPerms)) + + stat, err := os.Stat(dirPath) + require.NoError(t, err) + require.True(t, stat.IsDir()) + require.Equal(t, dirPerms, stat.Mode().Perm()) + + t.Log("Create a read-only file inside directory") + file := filepath.Join(dirPath, "file1") + // write file as read-only to verify EnsureEmptyDirectory can still delete it. + require.NoError(t, os.WriteFile(file, []byte("test"), ownerReadOnlyDirMode)) + + t.Log("Create a read-only sub-directory inside directory") + subDir := filepath.Join(dirPath, "subdir") + // write subDir as read-execute-only to verify EnsureEmptyDirectory can still delete it. + require.NoError(t, os.Mkdir(subDir, ownerReadOnlyDirMode)) + + t.Log("Call EnsureEmptyDirectory against directory with different permissions") + require.NoError(t, EnsureEmptyDirectory(dirPath, 0640)) + + t.Log("Ensure directory is now empty") + entries, err := os.ReadDir(dirPath) + require.NoError(t, err) + require.Empty(t, entries) + + t.Log("Ensure original directory permissions are unchanged") + stat, err = os.Stat(dirPath) + require.NoError(t, err) + require.Equal(t, dirPerms, stat.Mode().Perm()) +} + +func TestSetReadOnlyRecursive(t *testing.T) { + tempDir := t.TempDir() + targetFilePath := filepath.Join(tempDir, "target") + nestedDir := filepath.Join(tempDir, "nested") + filePath := filepath.Join(nestedDir, "testfile") + symlinkPath := filepath.Join(nestedDir, "symlink") + + t.Log("Create symlink target file outside directory with its own permissions") + // nolint:gosec + require.NoError(t, os.WriteFile(targetFilePath, []byte("something"), 0644)) + + t.Log("Create a nested directory structure that contains a file and sym. link") + require.NoError(t, os.Mkdir(nestedDir, ownerWritableDirMode)) + require.NoError(t, os.WriteFile(filePath, []byte("test"), ownerWritableFileMode)) + require.NoError(t, os.Symlink(targetFilePath, symlinkPath)) + + t.Log("Set directory structure as read-only via DeleteReadOnlyRecursive") + require.NoError(t, SetReadOnlyRecursive(nestedDir)) + + t.Log("Check file permissions") + stat, err := os.Stat(filePath) + require.NoError(t, err) + require.Equal(t, ownerReadOnlyFileMode, stat.Mode().Perm()) + + t.Log("Check directory permissions") + nestedStat, err := os.Stat(nestedDir) + require.NoError(t, err) + require.Equal(t, ownerReadOnlyDirMode, nestedStat.Mode().Perm()) + + t.Log("Check symlink target file permissions - should not be affected") + stat, err = os.Stat(targetFilePath) + require.NoError(t, err) + require.Equal(t, fs.FileMode(0644), stat.Mode().Perm()) + + t.Log("Make directory writable to enable test clean-up") + require.NoError(t, SetWritableRecursive(tempDir)) +} + +func TestSetWritableRecursive(t *testing.T) { + tempDir := t.TempDir() + targetFilePath := filepath.Join(tempDir, "target") + nestedDir := filepath.Join(tempDir, "nested") + filePath := filepath.Join(nestedDir, "testfile") + symlinkPath := filepath.Join(nestedDir, "symlink") + + t.Log("Create symlink target file outside directory with its own permissions") + // nolint:gosec + require.NoError(t, os.WriteFile(targetFilePath, []byte("something"), 0644)) + + t.Log("Create a nested directory (writable) structure that contains a file (read-only) and sym. link") + require.NoError(t, os.Mkdir(nestedDir, ownerWritableDirMode)) + require.NoError(t, os.WriteFile(filePath, []byte("test"), ownerReadOnlyFileMode)) + require.NoError(t, os.Symlink(targetFilePath, symlinkPath)) + + t.Log("Make directory read-only") + require.NoError(t, os.Chmod(nestedDir, ownerReadOnlyDirMode)) + + t.Log("Call SetWritableRecursive") + require.NoError(t, SetWritableRecursive(nestedDir)) + + t.Log("Check file is writable") + stat, err := os.Stat(filePath) + require.NoError(t, err) + require.Equal(t, ownerWritableFileMode, stat.Mode().Perm()) + + t.Log("Check directory is writable") + nestedStat, err := os.Stat(nestedDir) + require.NoError(t, err) + require.Equal(t, ownerWritableDirMode, nestedStat.Mode().Perm()) + + t.Log("Check symlink target file permissions - should not be affected") + stat, err = os.Stat(targetFilePath) + require.NoError(t, err) + require.Equal(t, fs.FileMode(0644), stat.Mode().Perm()) +} + +func TestDeleteReadOnlyRecursive(t *testing.T) { + tempDir := t.TempDir() + nestedDir := filepath.Join(tempDir, "nested") + filePath := filepath.Join(nestedDir, "testfile") + + t.Log("Create a nested read-only directory structure that contains a file and sym. link") + require.NoError(t, os.Mkdir(nestedDir, ownerWritableDirMode)) + require.NoError(t, os.WriteFile(filePath, []byte("test"), ownerReadOnlyFileMode)) + require.NoError(t, os.Chmod(nestedDir, ownerReadOnlyDirMode)) + + t.Log("Set directory structure as read-only via DeleteReadOnlyRecursive") + require.NoError(t, DeleteReadOnlyRecursive(nestedDir)) + + t.Log("Ensure directory was deleted") + _, err := os.Stat(nestedDir) + require.ErrorIs(t, err, os.ErrNotExist) +} + +func TestGetDirectoryModTime(t *testing.T) { + tempDir := t.TempDir() + unpackPath := filepath.Join(tempDir, "myimage") + + t.Log("Test case: unpack path does not exist") + modTime, err := GetDirectoryModTime(unpackPath) + require.ErrorIs(t, err, os.ErrNotExist) + require.True(t, modTime.IsZero()) + + t.Log("Test case: unpack path points to file") + require.NoError(t, os.WriteFile(unpackPath, []byte("test"), ownerWritableFileMode)) + + modTime, err = GetDirectoryModTime(filepath.Join(tempDir, "myimage")) + require.ErrorIs(t, err, ErrNotDirectory) + require.True(t, modTime.IsZero()) + + t.Log("Expect file still exists and then clean it up") + require.FileExists(t, unpackPath) + require.NoError(t, os.Remove(unpackPath)) + + t.Log("Test case: unpack path points to directory (happy path)") + require.NoError(t, os.Mkdir(unpackPath, ownerWritableDirMode)) + + modTime, err = GetDirectoryModTime(unpackPath) + require.NoError(t, err) + require.False(t, modTime.IsZero()) + + t.Log("Expect unpack time to match directory mod time") + stat, err := os.Stat(unpackPath) + require.NoError(t, err) + require.Equal(t, stat.ModTime(), modTime) +} diff --git a/internal/rukpak/util/hash.go b/internal/shared/util/hash/hash.go similarity index 62% rename from internal/rukpak/util/hash.go rename to internal/shared/util/hash/hash.go index a46bb34347..c1fac21bf5 100644 --- a/internal/rukpak/util/hash.go +++ b/internal/shared/util/hash/hash.go @@ -1,4 +1,4 @@ -package util +package hash import ( "crypto/sha256" @@ -7,10 +7,10 @@ import ( "math/big" ) -// DeepHashObject writes specified object to hash using the spew library -// which follows pointers and prints actual values of the nested objects -// ensuring the hash does not change when a pointer changes. -func DeepHashObject(obj interface{}) (string, error) { +// DeepHashObject writes specified object to hash based on the object's +// JSON representation. If the object fails JSON marshaling, DeepHashObject +// panics. +func DeepHashObject(obj interface{}) string { // While the most accurate encoding we could do for Kubernetes objects (runtime.Object) // would use the API machinery serializers, those operate over entire objects - and // we often need to operate on snippets. Checking with the experts and the implementation, @@ -22,18 +22,19 @@ func DeepHashObject(obj interface{}) (string, error) { // will be encoded hasher := sha256.New224() - hasher.Reset() encoder := json.NewEncoder(hasher) if err := encoder.Encode(obj); err != nil { - return "", fmt.Errorf("couldn't encode object: %w", err) + panic(fmt.Sprintf("couldn't encode object: %v", err)) } - // base62(sha224(bytes)) is a useful hash and encoding for adding the contents of this + // TODO: Investigate whether we can change this to base62(sha224(bytes)) + // The main concern with changing the base is that it will cause the hash + // function output to change, which may cause issues with consumers expecting + // a consistent hash. + // + // base36(sha224(bytes)) is a useful hash and encoding for adding the contents of this // to a Kubernetes identifier or other field which has length and character set requirements - var hash []byte - hash = hasher.Sum(hash) - var i big.Int - i.SetBytes(hash[:]) - return i.Text(36), nil + i.SetBytes(hasher.Sum(nil)) + return i.Text(36) } diff --git a/internal/rukpak/util/hash_test.go b/internal/shared/util/hash/hash_test.go similarity index 75% rename from internal/rukpak/util/hash_test.go rename to internal/shared/util/hash/hash_test.go index 3273793f7d..9e94ffc3eb 100644 --- a/internal/rukpak/util/hash_test.go +++ b/internal/shared/util/hash/hash_test.go @@ -1,24 +1,30 @@ -package util_test +package hash_test import ( + "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/operator-framework/operator-controller/internal/rukpak/util" + hashutil "github.com/operator-framework/operator-controller/internal/shared/util/hash" ) +type unmarshalable struct{} + +func (u *unmarshalable) MarshalJSON() ([]byte, error) { + return nil, errors.New("unmarshalable") +} + func TestDeepHashObject(t *testing.T) { tests := []struct { name string - wantErr bool + wantPanic bool obj interface{} expectedHash string }{ { - name: "populated obj with exported fields", - wantErr: false, + name: "populated obj with exported fields", obj: struct { Str string Num int @@ -37,8 +43,7 @@ func TestDeepHashObject(t *testing.T) { expectedHash: "gta1bt5sybll5qjqxdiekmjm7py93glrinmnrfb31fj", }, { - name: "modified populated obj with exported fields", - wantErr: false, + name: "modified populated obj with exported fields", obj: struct { Str string Num int @@ -57,8 +62,7 @@ func TestDeepHashObject(t *testing.T) { expectedHash: "1ftn1z2ieih8hsmi2a8c6mkoef6uodrtn4wtt1qapioh", }, { - name: "populated obj with unexported fields", - wantErr: false, + name: "populated obj with unexported fields", obj: struct { str string num int @@ -80,38 +84,42 @@ func TestDeepHashObject(t *testing.T) { // The JSON encoder requires exported fields or it will generate // the same hash as a completely empty object name: "empty obj", - wantErr: false, obj: struct{}{}, expectedHash: "16jfjhihxbzhfhs1k5mimq740kvioi98pfbea9q6qtf9", }, { name: "string a", - wantErr: false, obj: "a", expectedHash: "1lu1qv1451mq7gv9upu1cx8ffffi07rel5xvbvvc44dh", }, { name: "string b", - wantErr: false, obj: "b", expectedHash: "1ija85ah4gd0beltpfhszipkxfyqqxhp94tf2mjfgq61", }, { name: "nil obj", - wantErr: false, obj: nil, expectedHash: "2im0kl1kwvzn46sr4cdtkvmdzrlurvj51xdzhwdht8l0", }, + { + name: "unmarshalable obj", + obj: &unmarshalable{}, + wantPanic: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - hash, err := util.DeepHashObject(tc.obj) - if tc.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) + test := func() { + hash := hashutil.DeepHashObject(tc.obj) assert.Equal(t, tc.expectedHash, hash) } + + if tc.wantPanic { + require.Panics(t, test) + } else { + require.NotPanics(t, test) + } }) } } diff --git a/internal/shared/util/http/certlog.go b/internal/shared/util/http/certlog.go new file mode 100644 index 0000000000..b772f93f1e --- /dev/null +++ b/internal/shared/util/http/certlog.go @@ -0,0 +1,166 @@ +package http + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/go-logr/logr" +) + +const ( + defaultLogLevel = 4 +) + +// Log the certificates that would be used for docker pull operations +// Assumes a /etc/docker/certs.d like path, where the directory contains +// : directories in which a CA certificate (generally +// named "ca.crt") is located. +func LogDockerCertificates(path string, log logr.Logger) { + // These are the default paths that containers/images looks at for host:port certs + // See containers/images: docker/docker_client.go + paths := []string{"/etc/docker/certs.d", "/etc/containers/certs.d"} + if path != "" { + paths = []string{path} + } + for _, path := range paths { + fi, err := os.Stat(path) + if err != nil { + log.Error(err, "statting directory", "directory", path) + continue + } + if !fi.IsDir() { + log.V(defaultLogLevel).Info("not a directory", "directory", path) + continue + } + dirEntries, err := os.ReadDir(path) + if err != nil { + log.Error(err, "reading directory", "directory", path) + continue + } + for _, e := range dirEntries { + hostPath := filepath.Join(path, e.Name()) + fi, err := os.Stat(hostPath) + if err != nil { + log.Error(err, "dumping certs", "path", hostPath) + continue + } + if !fi.IsDir() { + log.V(defaultLogLevel).Info("ignoring non-directory", "path", hostPath) + continue + } + logPath(hostPath, "dump docker certs", log) + } + } +} + +// This function unwraps the given error to find an CertificateVerificationError. +// It then logs the list of certificates found in the unwrapped error +// Returns: +// * true if a CertificateVerificationError is found +// * false if no CertificateVerificationError is found +func LogCertificateVerificationError(err error, log logr.Logger) bool { + for err != nil { + var cvErr *tls.CertificateVerificationError + if errors.As(err, &cvErr) { + n := 1 + for _, cert := range cvErr.UnverifiedCertificates { + log.Error(err, "unverified cert", "n", n, "subject", cert.Subject, "issuer", cert.Issuer, "DNSNames", cert.DNSNames, "serial", cert.SerialNumber) + n = n + 1 + } + return true + } + err = errors.Unwrap(err) + } + return false +} + +func logPath(path, action string, log logr.Logger) { + fi, err := os.Stat(path) + if err != nil { + log.Error(err, "error in os.Stat()", "path", path) + return + } + if !fi.IsDir() { + logFile(path, "", fmt.Sprintf("%s file", action), log) + return + } + action = fmt.Sprintf("%s directory", action) + dirEntries, err := os.ReadDir(path) + if err != nil { + log.Error(err, "error in os.ReadDir()", "path", path) + return + } + for _, e := range dirEntries { + file := filepath.Join(path, e.Name()) + fi, err := os.Stat(file) + if err != nil { + log.Error(err, "error in os.Stat()", "file", file) + continue + } + if fi.IsDir() { + log.V(defaultLogLevel).Info("ignoring subdirectory", "directory", file) + continue + } + logFile(e.Name(), path, action, log) + } +} + +func logFile(filename, path, action string, log logr.Logger) { + filepath := filepath.Join(path, filename) + _, err := os.Stat(filepath) + if err != nil { + log.Error(err, "statting file", "file", filepath) + return + } + data, err := os.ReadFile(filepath) + if err != nil { + log.Error(err, "error in os.ReadFile()", "file", filepath) + return + } + if len(data) > 0 { + logPem(data, filename, path, action, log) + return + } + // Indicate that the file is empty + args := []any{"file", filename, "empty", "true"} + if path != "" { + args = append(args, "directory", path) + } + log.V(defaultLogLevel).Info(action, args...) +} + +func logPem(data []byte, filename, path, action string, log logr.Logger) { + for len(data) > 0 { + var block *pem.Block + block, data = pem.Decode(data) + if block == nil { + log.Info("error: no block returned from pem.Decode()", "file", filename) + return + } + crt, err := x509.ParseCertificate(block.Bytes) + if err != nil { + log.Error(err, "error in x509.ParseCertificate()", "file", filename) + return + } + + args := []any{} + if path != "" { + args = append(args, "directory", path) + } + // Find an appopriate certificate identifier + args = append(args, "file", filename) + if s := crt.Subject.String(); s != "" { + args = append(args, "subject", s) + } else if crt.DNSNames != nil { + args = append(args, "DNSNames", crt.DNSNames) + } else if s := crt.SerialNumber.String(); s != "" { + args = append(args, "serial", s) + } + log.V(defaultLogLevel).Info(action, args...) + } +} diff --git a/internal/shared/util/http/certpoolwatcher.go b/internal/shared/util/http/certpoolwatcher.go new file mode 100644 index 0000000000..da5e78ba93 --- /dev/null +++ b/internal/shared/util/http/certpoolwatcher.go @@ -0,0 +1,182 @@ +package http + +import ( + "context" + "crypto/x509" + "fmt" + "os" + "slices" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/go-logr/logr" +) + +type CertPoolWatcher struct { + generation int + dir string + sslCertPaths []string + mx sync.RWMutex + pool *x509.CertPool + log logr.Logger + watcher *fsnotify.Watcher + done chan bool + restart func(int) +} + +// Returns the current CertPool and the generation number +func (cpw *CertPoolWatcher) Get() (*x509.CertPool, int, error) { + cpw.mx.RLock() + defer cpw.mx.RUnlock() + if cpw.pool == nil { + return nil, 0, fmt.Errorf("no certificate pool available") + } + return cpw.pool.Clone(), cpw.generation, nil +} + +// Change the restart behavior +func (cpw *CertPoolWatcher) Restart(f func(int)) { + cpw.restart = f +} + +// Indicate that you're done with the CertPoolWatcher so it can terminate +// the watcher go func +func (cpw *CertPoolWatcher) Done() { + if cpw.watcher != nil { + cpw.done <- true + } +} + +func (cpw *CertPoolWatcher) Start(ctx context.Context) error { + var err error + cpw.pool, err = NewCertPool(cpw.dir, cpw.log) + if err != nil { + return err + } + + watchPaths := append(cpw.sslCertPaths, cpw.dir) + watchPaths = slices.DeleteFunc(watchPaths, deleteEmptyStrings) + + // Nothing was configured to be watched, which means this is + // using the SystemCertPool, so we still need to no error out + if len(watchPaths) == 0 { + cpw.log.Info("No paths to watch") + return nil + } + + cpw.watcher, err = fsnotify.NewWatcher() + if err != nil { + return err + } + + for _, p := range watchPaths { + if err := cpw.watcher.Add(p); err != nil { + cpw.watcher.Close() + cpw.watcher = nil + return err + } + logPath(p, "watching certificate", cpw.log) + } + + go func() { + for { + select { + case e := <-cpw.watcher.Events: + cpw.checkForRestart(e.Name) + cpw.drainEvents() + cpw.update(e.Name) + case err := <-cpw.watcher.Errors: + cpw.log.Error(err, "error watching certificate dir") + os.Exit(1) + case <-ctx.Done(): + cpw.Done() + case <-cpw.done: + err := cpw.watcher.Close() + if err != nil { + cpw.log.Error(err, "error closing watcher") + } + return + } + } + }() + return nil +} + +func NewCertPoolWatcher(caDir string, log logr.Logger) (*CertPoolWatcher, error) { + // If the SSL_CERT_DIR or SSL_CERT_FILE environment variables are + // specified, this means that we have some control over the system root + // location, thus they may change, thus we should watch those locations. + // + // BECAUSE THE SYSTEM POOL WILL NOT UPDATE, WE HAVE TO RESTART IF THERE + // CHANGES TO EITHER OF THESE LOCATIONS: SSL_CERT_DIR, SSL_CERT_FILE + // + sslCertDir := os.Getenv("SSL_CERT_DIR") + sslCertFile := os.Getenv("SSL_CERT_FILE") + log.V(defaultLogLevel).Info("SSL environment", "SSL_CERT_DIR", sslCertDir, "SSL_CERT_FILE", sslCertFile) + + sslCertPaths := append(strings.Split(sslCertDir, ":"), sslCertFile) + sslCertPaths = slices.DeleteFunc(sslCertPaths, deleteEmptyStrings) + + cpw := &CertPoolWatcher{ + generation: 1, + dir: caDir, + sslCertPaths: sslCertPaths, + log: log, + done: make(chan bool), + } + return cpw, nil +} + +func deleteEmptyStrings(p string) bool { + if p == "" { + return true + } + if _, err := os.Stat(p); err != nil { + return true + } + return false +} + +func (cpw *CertPoolWatcher) update(name string) { + cpw.log.Info("updating certificate pool", "file", name) + pool, err := NewCertPool(cpw.dir, cpw.log) + if err != nil { + cpw.log.Error(err, "error updating certificate pool") + os.Exit(1) + } + cpw.mx.Lock() + defer cpw.mx.Unlock() + cpw.pool = pool + cpw.generation++ +} + +func (cpw *CertPoolWatcher) checkForRestart(name string) { + for _, p := range cpw.sslCertPaths { + if strings.Contains(name, p) { + cpw.log.Info("restarting due to file change", "file", name) + if cpw.restart != nil { + cpw.restart(0) + } + } + } +} + +// Drain as many events as possible before doing anything +// Otherwise, we will be hit with an event for _every_ entry in the +// directory, and end up doing an update for each one +func (cpw *CertPoolWatcher) drainEvents() { + for { + drainTimer := time.NewTimer(time.Millisecond * 50) + select { + case <-drainTimer.C: + return + case e := <-cpw.watcher.Events: + cpw.checkForRestart(e.Name) + } + if !drainTimer.Stop() { + <-drainTimer.C + } + } +} diff --git a/internal/shared/util/http/certpoolwatcher_test.go b/internal/shared/util/http/certpoolwatcher_test.go new file mode 100644 index 0000000000..f7d0a9f82d --- /dev/null +++ b/internal/shared/util/http/certpoolwatcher_test.go @@ -0,0 +1,243 @@ +package http_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "path/filepath" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + "sigs.k8s.io/controller-runtime/pkg/log" + + httputil "github.com/operator-framework/operator-controller/internal/shared/util/http" +) + +func createCert(t *testing.T, name string) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + require.NoError(t, err) + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{name}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + IsCA: true, + + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + require.NoError(t, err) + + certOut, err := os.Create(name) + require.NoError(t, err) + + err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + require.NoError(t, err) + + err = certOut.Close() + require.NoError(t, err) + + // ignore the key +} + +func createTempCaDir(t *testing.T) string { + tmpCaDir, err := os.MkdirTemp("", "ca-dir") + require.NoError(t, err) + createCert(t, filepath.Join(tmpCaDir, "test1.pem")) + return tmpCaDir +} + +func TestCertPoolWatcherCaDir(t *testing.T) { + // create a temporary CA directory + tmpCaDir := createTempCaDir(t) + defer os.RemoveAll(tmpCaDir) + + os.Unsetenv("SSL_CERT_FILE") + os.Unsetenv("SSL_CERT_DIR") + + // Create the cert pool watcher + cpw, err := httputil.NewCertPoolWatcher(tmpCaDir, log.FromContext(context.Background())) + require.NoError(t, err) + require.NotNil(t, cpw) + defer cpw.Done() + restarted := &atomic.Bool{} + restarted.Store(false) + cpw.Restart(func(int) { restarted.Store(true) }) + err = cpw.Start(context.Background()) + require.NoError(t, err) + + // Get the original pool + firstPool, firstGen, err := cpw.Get() + require.NoError(t, err) + require.NotNil(t, firstPool) + + // Create a second cert in the CA directory + certName := filepath.Join(tmpCaDir, "test2.pem") + t.Logf("Create cert file at %q\n", certName) + createCert(t, certName) + + require.Eventually(t, func() bool { + secondPool, secondGen, err := cpw.Get() + if err != nil { + return false + } + // Should NOT restart, because this is not SSL_CERT_DIR nor SSL_CERT_FILE + return secondGen != firstGen && !firstPool.Equal(secondPool) && !restarted.Load() + }, 10*time.Second, time.Second) +} + +func TestCertPoolWatcherSslCertDir(t *testing.T) { + // create a temporary CA directory for SSL_CERT_DIR + tmpSslDir := createTempCaDir(t) + defer os.RemoveAll(tmpSslDir) + + // Update environment variables for the watcher - some of these should not exist + os.Unsetenv("SSL_CERT_FILE") + os.Setenv("SSL_CERT_DIR", tmpSslDir+":/tmp/does-not-exist.dir") + defer os.Unsetenv("SSL_CERT_DIR") + + // Create a different CaDir + tmpCaDir := createTempCaDir(t) + defer os.RemoveAll(tmpCaDir) + + // Create the cert pool watcher + cpw, err := httputil.NewCertPoolWatcher(tmpCaDir, log.FromContext(context.Background())) + require.NoError(t, err) + restarted := &atomic.Bool{} + restarted.Store(false) + cpw.Restart(func(int) { restarted.Store(true) }) + err = cpw.Start(context.Background()) + require.NoError(t, err) + defer cpw.Done() + + // Get the original pool + firstPool, firstGen, err := cpw.Get() + require.NoError(t, err) + require.NotNil(t, firstPool) + + // Create a second cert in SSL_CIR_DIR + certName := filepath.Join(tmpSslDir, "test2.pem") + t.Logf("Create cert file at %q\n", certName) + createCert(t, certName) + + require.Eventually(t, func() bool { + _, secondGen, err := cpw.Get() + if err != nil { + return false + } + // Because SSL_CERT_DIR is part of the SystemCertPool: + // 1. CPW only watches: it doesn't actually load it, that's the SystemCertPool's responsibility + // 2. Because the SystemCertPool never changes, we can't directly compare the pools + // 3. If SSL_CERT_DIR changes, we should expect a restart + return secondGen != firstGen && restarted.Load() + }, 10*time.Second, time.Second) +} + +func TestCertPoolWatcherSslCertFile(t *testing.T) { + // create a temporary directory + tmpDir, err := os.MkdirTemp("", "cert-pool") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // create the first cert + certName := filepath.Join(tmpDir, "test1.pem") + t.Logf("Create cert file at %q\n", certName) + createCert(t, certName) + + // Update environment variables for the watcher + os.Unsetenv("SSL_CERT_DIR") + os.Setenv("SSL_CERT_FILE", certName) + defer os.Unsetenv("SSL_CERT_FILE") + + // Create a different CaDir + tmpCaDir := createTempCaDir(t) + defer os.RemoveAll(tmpCaDir) + + // Create the cert pool watcher + cpw, err := httputil.NewCertPoolWatcher(tmpCaDir, log.FromContext(context.Background())) + require.NoError(t, err) + require.NotNil(t, cpw) + defer cpw.Done() + restarted := &atomic.Bool{} + restarted.Store(false) + cpw.Restart(func(int) { restarted.Store(true) }) + err = cpw.Start(context.Background()) + require.NoError(t, err) + + // Get the original pool + firstPool, firstGen, err := cpw.Get() + require.NoError(t, err) + require.NotNil(t, firstPool) + + // Update the SSL_CERT_FILE + t.Logf("Create cert file at %q\n", certName) + createCert(t, certName) + + require.Eventually(t, func() bool { + _, secondGen, err := cpw.Get() + if err != nil { + return false + } + // Because SSL_CERT_FILE is part of the SystemCertPool: + // 1. CPW only watches: it doesn't actually load it, that's the SystemCertPool's responsibility + // 2. Because the SystemCertPool never changes, we can't directly compare the pools + // 3. If SSL_CERT_FILE changes, we should expect a restart + return secondGen != firstGen && restarted.Load() + }, 10*time.Second, time.Second) +} + +func TestCertPoolWatcherEmpty(t *testing.T) { + os.Unsetenv("SSL_CERT_FILE") + os.Unsetenv("SSL_CERT_DIR") + + // Create the empty cert pool watcher + cpw, err := httputil.NewCertPoolWatcher("", log.FromContext(context.Background())) + require.NoError(t, err) + require.NotNil(t, cpw) + defer cpw.Done() + err = cpw.Start(context.Background()) + require.NoError(t, err) + + pool, _, err := cpw.Get() + require.NoError(t, err) + require.NotNil(t, pool) +} + +func TestCertPoolInvalidPath(t *testing.T) { + os.Unsetenv("SSL_CERT_FILE") + os.Unsetenv("SSL_CERT_DIR") + + // Create an invalid cert pool watcher + cpw, err := httputil.NewCertPoolWatcher("/this/path/should/not/exist", log.FromContext(context.Background())) + require.NoError(t, err) + require.NotNil(t, cpw) + defer cpw.Done() + err = cpw.Start(context.Background()) + require.Error(t, err) + + pool, _, err := cpw.Get() + require.Error(t, err) + require.Nil(t, pool) +} diff --git a/internal/shared/util/http/certutil.go b/internal/shared/util/http/certutil.go new file mode 100644 index 0000000000..4dd83f0f49 --- /dev/null +++ b/internal/shared/util/http/certutil.go @@ -0,0 +1,56 @@ +package http + +import ( + "crypto/x509" + "fmt" + "os" + "path/filepath" + + "github.com/go-logr/logr" +) + +func NewCertPool(caDir string, log logr.Logger) (*x509.CertPool, error) { + caCertPool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + if caDir == "" { + return caCertPool, nil + } + + dirEntries, err := os.ReadDir(caDir) + if err != nil { + return nil, err + } + count := 0 + + for _, e := range dirEntries { + file := filepath.Join(caDir, e.Name()) + // These might be symlinks pointing to directories, so use Stat() to resolve + fi, err := os.Stat(file) + if err != nil { + return nil, err + } + if fi.IsDir() { + log.V(defaultLogLevel).Info("skip directory", "name", e.Name()) + continue + } + log.V(defaultLogLevel).Info("reading certificate file", "name", e.Name(), "size", fi.Size(), "modtime", fi.ModTime()) + data, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("error reading cert file %q: %w", file, err) + } + // The return indicates if any certs were added + if caCertPool.AppendCertsFromPEM(data) { + count++ + } + logPem(data, e.Name(), caDir, "loading certificate", log) + } + + // Found no certs! + if count == 0 { + return nil, fmt.Errorf("no certificates found in %q", caDir) + } + + return caCertPool, nil +} diff --git a/internal/shared/util/http/certutil_test.go b/internal/shared/util/http/certutil_test.go new file mode 100644 index 0000000000..f998b61d58 --- /dev/null +++ b/internal/shared/util/http/certutil_test.go @@ -0,0 +1,38 @@ +package http_test + +import ( + "context" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/require" + + httputil "github.com/operator-framework/operator-controller/internal/shared/util/http" +) + +// The "good" test consists of 3 Amazon Root CAs, along with a "PRIVATE KEY" in one of the files +// The "empty" test includes a single file with no PEM contents +func TestNewCertPool(t *testing.T) { + caDirs := []struct { + dir string + msg string + }{ + {"../../../../testdata/certs/", `no certificates found in "../../../../testdata/certs/"`}, + {"../../../../testdata/certs/good", ""}, + {"../../../../testdata/certs/empty", `no certificates found in "../../../../testdata/certs/empty"`}, + } + + log, _ := logr.FromContext(context.Background()) + for _, caDir := range caDirs { + t.Logf("Loading certs from %q", caDir.dir) + pool, err := httputil.NewCertPool(caDir.dir, log) + if caDir.msg == "" { + require.NoError(t, err) + require.NotNil(t, pool) + } else { + require.Error(t, err) + require.Nil(t, pool) + require.ErrorContains(t, err, caDir.msg) + } + } +} diff --git a/internal/httputil/httputil.go b/internal/shared/util/http/httputil.go similarity index 96% rename from internal/httputil/httputil.go rename to internal/shared/util/http/httputil.go index d620866e4d..f5a982d2de 100644 --- a/internal/httputil/httputil.go +++ b/internal/shared/util/http/httputil.go @@ -1,4 +1,4 @@ -package httputil +package http import ( "crypto/tls" diff --git a/internal/shared/util/image/cache.go b/internal/shared/util/image/cache.go new file mode 100644 index 0000000000..a82505ed5e --- /dev/null +++ b/internal/shared/util/image/cache.go @@ -0,0 +1,261 @@ +package image + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "iter" + "os" + "path/filepath" + "slices" + "testing" + "time" + + "github.com/containerd/containerd/archive" + "github.com/google/renameio/v2" + "github.com/opencontainers/go-digest" + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "go.podman.io/image/v5/docker/reference" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/registry" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/operator-framework/operator-controller/internal/operator-controller/features" + errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error" + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" +) + +type LayerData struct { + MediaType string + Reader io.Reader + Index int + Err error +} + +type Cache interface { + Fetch(context.Context, string, reference.Canonical) (fs.FS, time.Time, error) + Store(context.Context, string, reference.Named, reference.Canonical, ocispecv1.Image, iter.Seq[LayerData]) (fs.FS, time.Time, error) + Delete(context.Context, string) error + GarbageCollect(context.Context, string, reference.Canonical) error +} + +const ConfigDirLabel = "operators.operatorframework.io.index.configs.v1" + +func CatalogCache(basePath string) Cache { + return &diskCache{ + basePath: basePath, + filterFunc: filterForCatalogImage(), + } +} + +func filterForCatalogImage() func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) { + _, specIsCanonical := srcRef.(reference.Canonical) + + dirToUnpack, ok := image.Config.Labels[ConfigDirLabel] + if !ok { + // If the spec is a tagged keep, retries could end up resolving a new digest, where the label + // might show up. If the spec is canonical, no amount of retries will make the label appear. + // Therefore, we treat the error as terminal if the reference from the spec is canonical. + return nil, errorutil.WrapTerminal(fmt.Errorf("catalog image is missing the required label %q", ConfigDirLabel), specIsCanonical) + } + + return allFilters( + onlyPath(dirToUnpack), + forceOwnershipRWX(), + ), nil + } +} + +func BundleCache(basePath string) Cache { + return &diskCache{ + basePath: basePath, + filterFunc: filterForBundleImage(), + } +} + +func filterForBundleImage() func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return forceOwnershipRWX(), nil + } +} + +type diskCache struct { + basePath string + filterFunc func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) +} + +func (a *diskCache) Fetch(ctx context.Context, ownerID string, canonicalRef reference.Canonical) (fs.FS, time.Time, error) { + l := log.FromContext(ctx) + unpackPath := a.unpackPath(ownerID, canonicalRef.Digest()) + modTime, err := fsutil.GetDirectoryModTime(unpackPath) + switch { + case errors.Is(err, os.ErrNotExist): + return nil, time.Time{}, nil + case errors.Is(err, fsutil.ErrNotDirectory): + l.Info("unpack path is not a directory; attempting to delete", "path", unpackPath) + return nil, time.Time{}, fsutil.DeleteReadOnlyRecursive(unpackPath) + case err != nil: + return nil, time.Time{}, fmt.Errorf("error checking image content already unpacked: %w", err) + } + l.Info("image already unpacked") + return os.DirFS(a.unpackPath(ownerID, canonicalRef.Digest())), modTime, nil +} + +func (a *diskCache) ownerIDPath(ownerID string) string { + return filepath.Join(a.basePath, ownerID) +} + +func (a *diskCache) unpackPath(ownerID string, digest digest.Digest) string { + return filepath.Join(a.ownerIDPath(ownerID), digest.String()) +} + +type LayerUnpacker interface { + Unpack(_ context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error +} + +type defaultLayerUnpacker struct{} + +type chartLayerUnpacker struct{} + +var _ LayerUnpacker = &defaultLayerUnpacker{} +var _ LayerUnpacker = &chartLayerUnpacker{} + +func imageLayerUnpacker(layer LayerData) LayerUnpacker { + if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) || testing.Testing() { + if layer.MediaType == registry.ChartLayerMediaType { + return &chartLayerUnpacker{} + } + } + return &defaultLayerUnpacker{} +} + +func (u *chartLayerUnpacker) Unpack(_ context.Context, path string, layer LayerData, _ ...archive.ApplyOpt) error { + if err := storeChartLayer(path, layer); err != nil { + return fmt.Errorf("error applying chart layer[%d]: %w", layer.Index, err) + } + return nil +} + +func (u *defaultLayerUnpacker) Unpack(ctx context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error { + if _, err := archive.Apply(ctx, path, layer.Reader, opts...); err != nil { + return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err) + } + return nil +} + +func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, imgCfg ocispecv1.Image, layers iter.Seq[LayerData]) (fs.FS, time.Time, error) { + var applyOpts []archive.ApplyOpt + if a.filterFunc != nil { + filter, err := a.filterFunc(ctx, srcRef, imgCfg) + if err != nil { + return nil, time.Time{}, err + } + applyOpts = append(applyOpts, archive.WithFilter(filter)) + } + + dest := a.unpackPath(ownerID, canonicalRef.Digest()) + if err := fsutil.EnsureEmptyDirectory(dest, 0700); err != nil { + return nil, time.Time{}, fmt.Errorf("error ensuring empty unpack directory: %w", err) + } + + if err := func() error { + l := log.FromContext(ctx) + l.Info("unpacking image", "path", dest) + for layer := range layers { + if layer.Err != nil { + return fmt.Errorf("error reading layer[%d]: %w", layer.Index, layer.Err) + } + layerUnpacker := imageLayerUnpacker(layer) + if err := layerUnpacker.Unpack(ctx, dest, layer, applyOpts...); err != nil { + return fmt.Errorf("unpacking layer: %w", err) + } + l.Info("applied layer", "layer", layer.Index) + } + if err := fsutil.SetReadOnlyRecursive(dest); err != nil { + return fmt.Errorf("error making unpack directory read-only: %w", err) + } + return nil + }(); err != nil { + return nil, time.Time{}, errors.Join(err, fsutil.DeleteReadOnlyRecursive(dest)) + } + modTime, err := fsutil.GetDirectoryModTime(dest) + if err != nil { + return nil, time.Time{}, fmt.Errorf("error getting mod time of unpack directory: %w", err) + } + return os.DirFS(dest), modTime, nil +} + +func storeChartLayer(path string, layer LayerData) error { + if layer.Err != nil { + return fmt.Errorf("error found in layer data: %w", layer.Err) + } + data, err := io.ReadAll(layer.Reader) + if err != nil { + return fmt.Errorf("error reading layer[%d]: %w", layer.Index, err) + } + meta := new(chart.Metadata) + _, err = inspectChart(data, meta) + if err != nil { + return fmt.Errorf("inspecting chart layer: %w", err) + } + chart, err := renameio.TempFile("", + filepath.Join(path, + fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version), + ), + ) + if err != nil { + return fmt.Errorf("create temp file: %w", err) + } + defer func() { + _ = chart.Cleanup() + }() + if _, err := io.Copy(chart, bytes.NewReader(data)); err != nil { + return fmt.Errorf("copying chart archive: %w", err) + } + _, err = chart.Seek(0, io.SeekStart) + if err != nil { + return fmt.Errorf("seek chart archive start: %w", err) + } + return chart.CloseAtomicallyReplace() +} + +func (a *diskCache) Delete(_ context.Context, ownerID string) error { + return fsutil.DeleteReadOnlyRecursive(a.ownerIDPath(ownerID)) +} + +func (a *diskCache) GarbageCollect(_ context.Context, ownerID string, keep reference.Canonical) error { + ownerIDPath := a.ownerIDPath(ownerID) + dirEntries, err := os.ReadDir(ownerIDPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("error reading owner directory: %w", err) + } + + foundKeep := false + dirEntries = slices.DeleteFunc(dirEntries, func(entry os.DirEntry) bool { + found := entry.Name() == keep.Digest().String() + if found { + foundKeep = true + } + return found + }) + + for _, dirEntry := range dirEntries { + if err := fsutil.DeleteReadOnlyRecursive(filepath.Join(ownerIDPath, dirEntry.Name())); err != nil { + return fmt.Errorf("error removing entry %s: %w", dirEntry.Name(), err) + } + } + + if !foundKeep { + if err := fsutil.DeleteReadOnlyRecursive(ownerIDPath); err != nil { + return fmt.Errorf("error deleting unused owner data: %w", err) + } + } + return nil +} diff --git a/internal/shared/util/image/cache_test.go b/internal/shared/util/image/cache_test.go new file mode 100644 index 0000000000..5f5e51b50d --- /dev/null +++ b/internal/shared/util/image/cache_test.go @@ -0,0 +1,807 @@ +package image + +import ( + "archive/tar" + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "iter" + "os" + "path/filepath" + "strings" + "syscall" + "testing" + "testing/fstest" + "time" + + "github.com/containerd/containerd/archive" + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.podman.io/image/v5/docker/reference" + "helm.sh/helm/v3/pkg/registry" + + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" +) + +func TestDiskCacheFetch(t *testing.T) { + const myOwner = "myOwner" + myRef := mustParseCanonical(t, "my.registry.io/ns/repo@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") + + testCases := []struct { + name string + ownerID string + ref reference.Canonical + setup func(*testing.T, *diskCache) + expect func(*testing.T, *diskCache, fs.FS, time.Time, error) + }{ + { + name: "all zero-values when owner does not exist", + ownerID: myOwner, + ref: myRef, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.NoError(t, err) + }, + }, + { + name: "all zero values when digest does not exist for owner", + ownerID: myOwner, + ref: myRef, + setup: func(t *testing.T, cache *diskCache) { + require.NoError(t, os.MkdirAll(cache.ownerIDPath(myOwner), 0777)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.NoError(t, err) + }, + }, + { + name: "owners do not share data", + ownerID: myOwner, + ref: myRef, + setup: func(t *testing.T, cache *diskCache) { + require.NoError(t, os.MkdirAll(cache.unpackPath("otherOwner", myRef.Digest()), 0777)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.NoError(t, err) + }, + }, + { + name: "permission error when owner directory cannot be read", + ownerID: myOwner, + ref: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerIDPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerIDPath, 0700)) + require.NoError(t, os.Chmod(ownerIDPath, 0000)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.ErrorIs(t, err, os.ErrPermission) + }, + }, + { + name: "unexpected contents for a reference are deleted, zero values returned", + ownerID: myOwner, + ref: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerIDPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerIDPath, 0700)) + require.NoError(t, os.WriteFile(cache.unpackPath(myOwner, myRef.Digest()), []byte{}, 0600)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.NoError(t, err) // nolint:testifylint + assert.NoFileExists(t, cache.unpackPath(myOwner, myRef.Digest())) + }, + }, + { + name: "digest exists for owner", + ownerID: myOwner, + ref: myRef, + setup: func(t *testing.T, cache *diskCache) { + unpackPath := cache.unpackPath(myOwner, myRef.Digest()) + require.NoError(t, os.MkdirAll(cache.ownerIDPath(myOwner), 0700)) + require.NoError(t, os.MkdirAll(unpackPath, 0700)) + require.NoError(t, os.WriteFile(filepath.Join(unpackPath, "my-file"), []byte("my-data"), 0600)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + + // Verify fsys + data, err := fs.ReadFile(fsys, "my-file") + require.NoError(t, err) + assert.Equal(t, "my-data", string(data)) + + // Verify modTime + dirStat, err := os.Stat(cache.unpackPath(myOwner, myRef.Digest())) + assert.Equal(t, dirStat.ModTime(), modTime) + + // Verify no error + assert.NoError(t, err) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dc := &diskCache{basePath: t.TempDir()} + if tc.setup != nil { + tc.setup(t, dc) + } + fsys, modTime, err := dc.Fetch(context.Background(), tc.ownerID, tc.ref) + require.NotNil(t, tc.expect, "test case must include an expect function") + tc.expect(t, dc, fsys, modTime, err) + require.NoError(t, fsutil.SetWritableRecursive(dc.basePath)) + }) + } +} + +func TestDiskCacheStore_HelmChart(t *testing.T) { + const myOwner = "myOwner" + myCanonicalRef := mustParseCanonical(t, "my.registry.io/ns/chart@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") + myTaggedRef, err := reference.WithTag(reference.TrimNamed(myCanonicalRef), "test-tag") + require.NoError(t, err) + + testCases := []struct { + name string + ownerID string + srcRef reference.Named + canonicalRef reference.Canonical + imgConfig ocispecv1.Image + layers iter.Seq[LayerData] + filterFunc func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) + setup func(*testing.T, *diskCache) + expect func(*testing.T, *diskCache, fs.FS, time.Time, error) + }{ + { + name: "returns no error if layer read contains helm chart", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + layers: func() iter.Seq[LayerData] { + testChart := mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + ) + return func(yield func(LayerData) bool) { + yield(LayerData{Reader: bytes.NewBuffer(testChart), MediaType: registry.ChartLayerMediaType}) + } + }(), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dc := &diskCache{ + basePath: t.TempDir(), + filterFunc: tc.filterFunc, + } + if tc.setup != nil { + tc.setup(t, dc) + } + fsys, modTime, err := dc.Store(context.Background(), tc.ownerID, tc.srcRef, tc.canonicalRef, tc.imgConfig, tc.layers) + require.NotNil(t, tc.expect, "test case must include an expect function") + tc.expect(t, dc, fsys, modTime, err) + require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) + }) + } +} + +func TestDiskCacheStore(t *testing.T) { + const myOwner = "myOwner" + myCanonicalRef := mustParseCanonical(t, "my.registry.io/ns/repo@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") + myTaggedRef, err := reference.WithTag(reference.TrimNamed(myCanonicalRef), "test-tag") + require.NoError(t, err) + + myUID := os.Getuid() + myGID := os.Getgid() + + testCases := []struct { + name string + ownerID string + srcRef reference.Named + canonicalRef reference.Canonical + imgConfig ocispecv1.Image + layers iter.Seq[LayerData] + filterFunc func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) + setup func(*testing.T, *diskCache) + expect func(*testing.T, *diskCache, fs.FS, time.Time, error) + }{ + { + name: "returns error when filter func fails", + filterFunc: func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) { + return nil, errors.New("filterfunc error") + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Zero(t, modTime) + assert.ErrorContains(t, err, "filterfunc error") + }, + }, + { + name: "returns permission error when base path is not writeable", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + setup: func(t *testing.T, cache *diskCache) { + require.NoError(t, os.Chmod(cache.basePath, 0400)) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.ErrorIs(t, err, os.ErrPermission) + }, + }, + { + name: "returns error if layer data contains error", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + layers: func(yield func(LayerData) bool) { + yield(LayerData{Err: errors.New("layer error")}) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.ErrorContains(t, err, "layer error") + }, + }, + { + name: "returns error if layer read returns error", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + layers: func(yield func(LayerData) bool) { + yield(LayerData{Reader: strings.NewReader("hello :)")}) + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + assert.ErrorContains(t, err, "error applying layer") + }, + }, + { + name: "no error and an empty FS returned when there are no layers", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + layers: layerFSIterator(), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + + entries, err := fs.ReadDir(fsys, ".") + assert.NoError(t, err) // nolint:testifylint + assert.Empty(t, entries) + }, + }, + { + name: "multiple layers with whiteouts are stored as expected", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + layers: layerFSIterator( + fstest.MapFS{ + "foo": &fstest.MapFile{Data: []byte("foo_layer1"), Mode: 0600}, + "bar": &fstest.MapFile{Data: []byte("bar_layer1"), Mode: 0600}, + "fizz": &fstest.MapFile{Data: []byte("fizz_layer1"), Mode: 0600}, + }, + fstest.MapFS{ + "foo": &fstest.MapFile{Data: []byte("foo_layer2"), Mode: 0600}, + ".wh.bar": &fstest.MapFile{Mode: 0600}, + "baz": &fstest.MapFile{Data: []byte("baz_layer2"), Mode: 0600}, + }, + ), + filterFunc: func(ctx context.Context, named reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return forceOwnershipRWX(), nil + }, + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + require.NotNil(t, fsys) + + fooData, fooErr := fs.ReadFile(fsys, "foo") + barData, barErr := fs.ReadFile(fsys, "bar") + bazData, bazErr := fs.ReadFile(fsys, "baz") + fizzData, fizzErr := fs.ReadFile(fsys, "fizz") + + assert.Equal(t, "foo_layer2", string(fooData)) + assert.NoError(t, fooErr) //nolint:testifylint + + assert.Equal(t, "baz_layer2", string(bazData)) + assert.NoError(t, bazErr) //nolint:testifylint + + assert.Empty(t, barData) + assert.ErrorIs(t, barErr, fs.ErrNotExist) //nolint:testifylint + + assert.Equal(t, "fizz_layer1", string(fizzData)) + assert.NoError(t, fizzErr) //nolint:testifylint + }, + }, + { + name: "uses filter", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + filterFunc: func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) { + return allFilters( + func(h *tar.Header) (bool, error) { + if h.Name == "foo" { + return false, nil + } + h.Name += ".txt" + return true, nil + }, + forceOwnershipRWX(), + ), nil + }, + layers: layerFSIterator( + fstest.MapFS{ + "foo": &fstest.MapFile{Data: []byte("foo_layer1"), Mode: 0600}, + "bar": &fstest.MapFile{Data: []byte("bar_layer1"), Mode: 0600}, + }, + ), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + + _, fooErr := fs.Stat(fsys, "foo") + assert.ErrorIs(t, fooErr, fs.ErrNotExist) //nolint:testifylint + + _, barErr := fs.Stat(fsys, "bar") + assert.ErrorIs(t, barErr, fs.ErrNotExist) //nolint:testifylint + + _, barTxtStat := fs.Stat(fsys, "bar.txt") + assert.NoError(t, barTxtStat) //nolint:testifylint + }, + }, + { + name: "uses bundle filter", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + filterFunc: filterForBundleImage(), + layers: layerFSIterator( + fstest.MapFS{ + "foo": &fstest.MapFile{Data: []byte("foo_layer1"), Mode: 0000}, + "bar": &fstest.MapFile{Data: []byte("bar_layer1"), Mode: 0000}, + }, + ), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + + fooStat, fooErr := fs.Stat(fsys, "foo") + barStat, barErr := fs.Stat(fsys, "bar") + + // You may have expected 0700 here, but after the files are stored, + // the cache recursively sets them as read-only. The fact that these + // files even exist proves that the filter executed properly because + // they are UID/GID: 0/0 in the fs layer, which we would not have + // been permitted to write to disk + assert.Equal(t, fs.FileMode(0400).String(), fooStat.Mode().String()) + assert.Equal(t, fs.FileMode(0400).String(), barStat.Mode().String()) + assert.Equal(t, myUID, int(fooStat.Sys().(*syscall.Stat_t).Uid)) + assert.Equal(t, myGID, int(fooStat.Sys().(*syscall.Stat_t).Gid)) + assert.Equal(t, myUID, int(barStat.Sys().(*syscall.Stat_t).Uid)) + assert.Equal(t, myGID, int(barStat.Sys().(*syscall.Stat_t).Gid)) + + assert.NoError(t, fooErr) + assert.NoError(t, barErr) + }, + }, + { + name: "fails if catalog filter cannot find expected image label", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + filterFunc: filterForCatalogImage(), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.ErrorContains(t, err, "catalog image is missing the required label") + }, + }, + { + name: "catalog filter includes only files under label's directory tree", + ownerID: myOwner, + srcRef: myTaggedRef, + canonicalRef: myCanonicalRef, + filterFunc: filterForCatalogImage(), + imgConfig: ocispecv1.Image{ + Config: ocispecv1.ImageConfig{ + Labels: map[string]string{ + ConfigDirLabel: "my-fav-configs", + }, + }, + }, + layers: layerFSIterator( + fstest.MapFS{ + "foo": &fstest.MapFile{Data: []byte("foo_layer1"), Mode: 0000}, + "my-fav-configs/catalog.json": &fstest.MapFile{Data: []byte(`{}`), Mode: 0000}, + }, + ), + expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { + require.NoError(t, err) + + _, fooErr := fs.Stat(fsys, "foo") + assert.ErrorIs(t, fooErr, fs.ErrNotExist) //nolint:testifylint + + catalogDataStat, catalogDataErr := fs.Stat(fsys, "my-fav-configs/catalog.json") + assert.NoError(t, catalogDataErr) // nolint:testifylint + + // You may have expected 0700 here, but after the files are stored, + // the cache recursively sets them as read-only. The fact that these + // files even exist proves that the filter executed properly because + // they are UID/GID: 0/0 in the fs layer, which we would not have + // been permitted to write to disk + assert.Equal(t, fs.FileMode(0400).String(), catalogDataStat.Mode().String()) + assert.Equal(t, myUID, int(catalogDataStat.Sys().(*syscall.Stat_t).Uid)) + assert.Equal(t, myGID, int(catalogDataStat.Sys().(*syscall.Stat_t).Gid)) + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dc := &diskCache{ + basePath: t.TempDir(), + filterFunc: tc.filterFunc, + } + if tc.setup != nil { + tc.setup(t, dc) + } + fsys, modTime, err := dc.Store(context.Background(), tc.ownerID, tc.srcRef, tc.canonicalRef, tc.imgConfig, tc.layers) + require.NotNil(t, tc.expect, "test case must include an expect function") + tc.expect(t, dc, fsys, modTime, err) + require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) + }) + } +} + +func TestDiskCacheDelete(t *testing.T) { + const myOwner = "myOwner" + + testCases := []struct { + name string + ownerID string + setup func(*testing.T, *diskCache) + expect func(*testing.T, *diskCache, error) + }{ + { + name: "no error when owner does not exist", + ownerID: myOwner, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) // nolint:testifylint + assert.NoDirExists(t, cache.ownerIDPath(myOwner)) + }, + }, + { + name: "does not delete a different owner", + ownerID: myOwner, + setup: func(t *testing.T, cache *diskCache) { + require.NoError(t, os.MkdirAll(cache.ownerIDPath("otherOwner"), 0500)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) //nolint:testifylint + assert.DirExists(t, cache.ownerIDPath("otherOwner")) + assert.NoDirExists(t, cache.ownerIDPath(myOwner)) + }, + }, + { + name: "deletes read-only owner", + ownerID: myOwner, + setup: func(t *testing.T, cache *diskCache) { + ownerIDPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerIDPath, 0700)) + require.NoError(t, os.MkdirAll(cache.unpackPath(myOwner, "subdir"), 0700)) + require.NoError(t, fsutil.SetReadOnlyRecursive(ownerIDPath)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) // nolint:testifylint + assert.NoDirExists(t, cache.ownerIDPath(myOwner)) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dc := &diskCache{basePath: t.TempDir()} + if tc.setup != nil { + tc.setup(t, dc) + } + err := dc.Delete(context.Background(), tc.ownerID) + require.NotNil(t, tc.expect, "test case must include an expect function") + tc.expect(t, dc, err) + require.NoError(t, fsutil.SetWritableRecursive(dc.basePath)) + }) + } +} + +func TestDiskCacheGarbageCollection(t *testing.T) { + const myOwner = "myOwner" + myRef := mustParseCanonical(t, "my.registry.io/ns/repo@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") + + testCases := []struct { + name string + ownerID string + keep reference.Canonical + setup func(*testing.T, *diskCache) + expect func(*testing.T, *diskCache, error) + }{ + { + name: "error when owner ID path is not readable", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerPath, 0700)) + require.NoError(t, os.Chmod(ownerPath, 0000)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.ErrorIs(t, err, os.ErrPermission) + }, + }, + { + name: "error when owner ID path is not writeable and contains gc-able content", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerPath, 0700)) + require.NoError(t, os.MkdirAll(cache.unpackPath(myOwner, "subdir"), 0700)) + require.NoError(t, os.Chmod(ownerPath, 0500)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.ErrorIs(t, err, os.ErrPermission) + }, + }, + { + name: "error when base path is not writeable and contains gc-able content", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerPath, 0700)) + require.NoError(t, os.MkdirAll(cache.unpackPath(myOwner, "subdir"), 0700)) + require.NoError(t, os.Chmod(cache.basePath, 0500)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.ErrorIs(t, err, os.ErrPermission) + }, + }, + { + name: "no error when owner does not exist", + ownerID: myOwner, + keep: myRef, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) + }, + }, + { + name: "no error when owner has no contents, deletes owner dir", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + ownerPath := cache.ownerIDPath(myOwner) + require.NoError(t, os.MkdirAll(ownerPath, 0700)) + require.NoError(t, fsutil.SetReadOnlyRecursive(ownerPath)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) //nolint:testifylint + assert.NoDirExists(t, cache.ownerIDPath(myOwner)) + }, + }, + { + name: "no error when owner does not have keep reference, deletes owner dir", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + unpackPath := cache.unpackPath(myOwner, "subdir") + require.NoError(t, os.MkdirAll(cache.ownerIDPath(myOwner), 0700)) + require.NoError(t, os.MkdirAll(unpackPath, 0700)) + require.NoError(t, fsutil.SetReadOnlyRecursive(unpackPath)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) //nolint:testifylint + assert.NoDirExists(t, cache.ownerIDPath(myOwner)) + }, + }, + { + name: "deletes everything _except_ keep's data", + ownerID: myOwner, + keep: myRef, + setup: func(t *testing.T, cache *diskCache) { + otherPath := cache.unpackPath(myOwner, "subdir") + unpackPath := cache.unpackPath(myOwner, myRef.Digest()) + require.NoError(t, os.MkdirAll(cache.ownerIDPath(myOwner), 0700)) + require.NoError(t, os.MkdirAll(otherPath, 0700)) + require.NoError(t, os.MkdirAll(unpackPath, 0700)) + require.NoError(t, fsutil.SetReadOnlyRecursive(otherPath)) + require.NoError(t, fsutil.SetReadOnlyRecursive(unpackPath)) + }, + expect: func(t *testing.T, cache *diskCache, err error) { + assert.NoError(t, err) //nolint:testifylint + assert.DirExists(t, cache.unpackPath(myOwner, myRef.Digest())) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dc := &diskCache{basePath: t.TempDir()} + if tc.setup != nil { + tc.setup(t, dc) + } + err := dc.GarbageCollect(context.Background(), tc.ownerID, tc.keep) + require.NotNil(t, tc.expect, "test case must include an expect function") + tc.expect(t, dc, err) + require.NoError(t, fsutil.SetWritableRecursive(dc.basePath)) + }) + } +} + +func Test_storeChartLayer(t *testing.T) { + tmp := t.TempDir() + type args struct { + path string + data LayerData + } + type want struct { + errStr string + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "store chart layer to given path", + args: args{ + path: tmp, + data: LayerData{ + Index: 0, + MediaType: registry.ChartLayerMediaType, + Reader: bytes.NewBuffer(mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + )), + }, + }, + }, + { + name: "store invalid chart layer", + args: args{ + path: tmp, + data: LayerData{ + Index: 0, + MediaType: registry.ChartLayerMediaType, + Reader: bytes.NewBuffer(mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + )), + }, + }, + }, + { + name: "store existing from dummy reader", + args: args{ + path: tmp, + data: LayerData{ + Index: 0, + MediaType: registry.ChartLayerMediaType, + Reader: &dummyReader{}, + }, + }, + want: want{ + errStr: "error reading layer[0]: something went wrong", + }, + }, + { + name: "handle chart layer data", + args: args{ + path: tmp, + data: LayerData{ + Index: 0, + MediaType: registry.ChartLayerMediaType, + Err: fmt.Errorf("invalid layer data"), + Reader: bytes.NewBuffer(mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + )), + }, + }, + want: want{ + errStr: "error found in layer data: invalid layer data", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := storeChartLayer(tc.args.path, tc.args.data) + if tc.want.errStr != "" { + require.Error(t, err) + require.EqualError(t, err, tc.want.errStr, "chart store error") + } else { + require.NoError(t, err) + } + }) + } +} + +func mustParseCanonical(t *testing.T, s string) reference.Canonical { + n, err := reference.ParseNamed(s) + require.NoError(t, err) + c, ok := n.(reference.Canonical) + require.True(t, ok, "image reference must be canonical") + return c +} + +func layerFSIterator(layerFilesystems ...fs.FS) iter.Seq[LayerData] { + return func(yield func(data LayerData) bool) { + for i, fsys := range layerFilesystems { + rc := fsTarReader(fsys) + ld := LayerData{ + Reader: rc, + Index: i, + } + stop := !yield(ld) + _ = rc.Close() + if stop { + return + } + } + } +} + +func fsTarReader(fsys fs.FS) io.ReadCloser { + pr, pw := io.Pipe() + tw := tar.NewWriter(pw) + go func() { + err := tw.AddFS(fsys) + _ = pw.CloseWithError(err) + }() + return pr +} + +type dummyReader struct{} + +var _ io.Reader = &dummyReader{} + +func (r *dummyReader) Read(p []byte) (int, error) { + return 0, errors.New("something went wrong") +} diff --git a/internal/shared/util/image/filters.go b/internal/shared/util/image/filters.go new file mode 100644 index 0000000000..1bf2826932 --- /dev/null +++ b/internal/shared/util/image/filters.go @@ -0,0 +1,67 @@ +package image + +import ( + "archive/tar" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/containerd/containerd/archive" +) + +// forceOwnershipRWX is a passthrough archive.Filter that sets a tar header's +// Uid and Gid to the current process's Uid and Gid and ensures its permissions +// give the owner full read/write/execute permission. The process Uid and Gid +// are determined when forceOwnershipRWX is called, not when the filter function +// is called. +func forceOwnershipRWX() archive.Filter { + uid := os.Getuid() + gid := os.Getgid() + return func(h *tar.Header) (bool, error) { + h.Uid = uid + h.Gid = gid + h.Mode |= 0700 + h.PAXRecords = nil + h.Xattrs = nil //nolint:staticcheck + return true, nil + } +} + +// onlyPath is an archive.Filter that keeps only files and directories that match p, or +// (if p is a directory) are present under p. onlyPath does not remap files to a new location. +// If an error occurs while comparing the desired path prefix with the tar header's name, the +// filter will return false with that error. +func onlyPath(p string) archive.Filter { + wantPath := path.Clean(strings.TrimPrefix(p, "/")) + return func(h *tar.Header) (bool, error) { + headerPath := path.Clean(strings.TrimPrefix(h.Name, "/")) + relPath, err := filepath.Rel(wantPath, headerPath) + if err != nil { + return false, fmt.Errorf("error getting relative path: %w", err) + } + if relPath == ".." || strings.HasPrefix(relPath, "../") { + return false, nil + } + return true, nil + } +} + +// allFilters is a composite archive.Filter that executes each filter in the order +// they are given. If any filter returns false or an error, the composite filter will immediately +// return that result to the caller, and no further filters are executed. +func allFilters(filters ...archive.Filter) archive.Filter { + return func(h *tar.Header) (bool, error) { + for _, filter := range filters { + keep, err := filter(h) + if err != nil { + return false, err + } + if !keep { + return false, nil + } + } + return true, nil + } +} diff --git a/internal/shared/util/image/filters_test.go b/internal/shared/util/image/filters_test.go new file mode 100644 index 0000000000..d3e81be5b9 --- /dev/null +++ b/internal/shared/util/image/filters_test.go @@ -0,0 +1,158 @@ +package image + +import ( + "archive/tar" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/rand" +) + +func TestForceOwnershipRWX(t *testing.T) { + h := tar.Header{ + Name: "foo/bar", + Mode: 0000, + Uid: rand.Int(), + Gid: rand.Int(), + Xattrs: map[string]string{ //nolint:staticcheck + "foo": "bar", + }, + PAXRecords: map[string]string{ + "fizz": "buzz", + }, + } + ok, err := forceOwnershipRWX()(&h) + require.NoError(t, err) + assert.True(t, ok) + + assert.Equal(t, "foo/bar", h.Name) + assert.Equal(t, int64(0700), h.Mode) + assert.Equal(t, os.Getuid(), h.Uid) + assert.Equal(t, os.Getgid(), h.Gid) + assert.Nil(t, h.PAXRecords) + assert.Nil(t, h.Xattrs) //nolint:staticcheck +} + +func TestOnlyPath(t *testing.T) { + type testCase struct { + name string + srcPaths []string + tarHeaders []tar.Header + assertion func(*tar.Header, bool, error) + } + for _, tc := range []testCase{ + { + name: "everything found when srcPaths represent root", + srcPaths: []string{"", "/"}, + tarHeaders: []tar.Header{ + { + Name: "file", + }, + { + Name: "/file", + }, + { + Name: "/nested/file", + }, + { + Name: "/deeply/nested/file", + }, + }, + assertion: func(tarHeader *tar.Header, keep bool, err error) { + assert.True(t, keep) + assert.NoError(t, err) + }, + }, + { + name: "nothing found outside of srcPath", + srcPaths: []string{"source"}, + tarHeaders: []tar.Header{ + { + Name: "elsewhere", + }, + { + Name: "/elsewhere", + }, + { + Name: "/nested/elsewhere", + }, + { + Name: "/deeply/nested/elsewhere", + }, + }, + assertion: func(tarHeader *tar.Header, keep bool, err error) { + assert.False(t, keep) + assert.NoError(t, err) + }, + }, + { + name: "absolute paths are trimmed", + srcPaths: []string{"source", "/source"}, + tarHeaders: []tar.Header{ + { + Name: "source", + }, + { + Name: "/source", + }, + { + Name: "source/nested/elsewhere", + }, + { + Name: "/source/nested/elsewhere", + }, + { + Name: "source/deeply/nested/elsewhere", + }, + { + Name: "/source/deeply/nested/elsewhere", + }, + }, + assertion: func(tarHeader *tar.Header, keep bool, err error) { + assert.True(t, keep) + assert.NoError(t, err) + }, + }, + { + name: "up level source paths are not supported", + srcPaths: []string{"../not-supported"}, + tarHeaders: []tar.Header{ + { + Name: "anything", + }, + }, + assertion: func(tarHeader *tar.Header, keep bool, err error) { + assert.False(t, keep) + assert.ErrorContains(t, err, "error getting relative path") + }, + }, + { + name: "up level tar headers are not supported", + srcPaths: []string{"fine"}, + tarHeaders: []tar.Header{ + { + Name: "../not-supported", + }, + { + Name: "../fine", + }, + }, + assertion: func(tarHeader *tar.Header, keep bool, err error) { + assert.False(t, keep) + assert.NoError(t, err) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + for _, srcPath := range tc.srcPaths { + f := onlyPath(srcPath) + for _, tarHeader := range tc.tarHeaders { + keep, err := f(&tarHeader) + tc.assertion(&tarHeader, keep, err) + } + } + }) + } +} diff --git a/internal/shared/util/image/helm.go b/internal/shared/util/image/helm.go new file mode 100644 index 0000000000..299f921df5 --- /dev/null +++ b/internal/shared/util/image/helm.go @@ -0,0 +1,175 @@ +package image + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/fs" + "iter" + "regexp" + "strings" + "time" + + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/pkg/blobinfocache/none" + "go.podman.io/image/v5/types" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/registry" +) + +func hasChart(imgCloser types.ImageCloser) bool { + config := imgCloser.ConfigInfo() + return config.MediaType == registry.ConfigMediaType +} + +func pullChart(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, imgSrc types.ImageSource, cache Cache) (fs.FS, time.Time, error) { + imgDigest := canonicalRef.Digest() + raw, _, err := imgSrc.GetManifest(ctx, &imgDigest) + if err != nil { + return nil, time.Time{}, fmt.Errorf("get OCI helm chart manifest; %w", err) + } + + chartManifest := ocispecv1.Manifest{} + if err := json.Unmarshal(raw, &chartManifest); err != nil { + return nil, time.Time{}, fmt.Errorf("unmarshaling chart manifest; %w", err) + } + + layerIter := iter.Seq[LayerData](func(yield func(LayerData) bool) { + for i, layer := range chartManifest.Layers { + ld := LayerData{Index: i, MediaType: layer.MediaType} + if layer.MediaType == registry.ChartLayerMediaType { + ld.Reader, _, ld.Err = imgSrc.GetBlob(ctx, + types.BlobInfo{ + Annotations: layer.Annotations, + MediaType: layer.MediaType, + Digest: layer.Digest, + Size: layer.Size, + }, + none.NoCache) + } + // Ignore the Helm provenance data layer + if layer.MediaType == registry.ProvLayerMediaType { + continue + } + if !yield(ld) { + return + } + } + }) + + return cache.Store(ctx, ownerID, srcRef, canonicalRef, ocispecv1.Image{}, layerIter) +} + +func IsValidChart(chart *chart.Chart) error { + if chart.Metadata == nil { + return errors.New("chart metadata is missing") + } + if chart.Metadata.Name == "" { + return errors.New("chart name is required") + } + if chart.Metadata.Version == "" { + return errors.New("chart version is required") + } + return chart.Metadata.Validate() +} + +type chartInspectionResult struct { + // templatesExist is set to true if the templates + // directory exists in the chart archive + templatesExist bool + // chartfileExists is set to true if the Chart.yaml + // file exists in the chart archive + chartfileExists bool +} + +func inspectChart(data []byte, metadata *chart.Metadata) (chartInspectionResult, error) { + report := chartInspectionResult{} + chart, err := loader.LoadArchive(bytes.NewBuffer(data)) + if err != nil { + return report, fmt.Errorf("loading chart archive: %w", err) + } + + report.templatesExist = len(chart.Templates) > 0 + report.chartfileExists = chart.Metadata != nil + + if metadata != nil && chart.Metadata != nil { + *metadata = *chart.Metadata + } + + return report, nil +} + +func IsBundleSourceChart(bundleFS fs.FS, metadata *chart.Metadata) (bool, error) { + var chartPath string + files, _ := fs.ReadDir(bundleFS, ".") + for _, file := range files { + if strings.HasSuffix(file.Name(), ".tgz") || + strings.HasSuffix(file.Name(), ".tar.gz") { + chartPath = file.Name() + break + } + } + + chartData, err := fs.ReadFile(bundleFS, chartPath) + if err != nil { + return false, err + } + + result, err := inspectChart(chartData, metadata) + if err != nil { + return false, fmt.Errorf("reading %s from fs: %w", chartPath, err) + } + + return (result.templatesExist && result.chartfileExists), nil +} + +type ChartOption func(*chart.Chart) + +func WithInstallNamespace(namespace string) ChartOption { + re := regexp.MustCompile(`{{\W+\.Release\.Namespace\W+}}`) + + return func(chrt *chart.Chart) { + for i, template := range chrt.Templates { + chrt.Templates[i].Data = re.ReplaceAll(template.Data, []byte(namespace)) + } + } +} + +func LoadChartFSWithOptions(bundleFS fs.FS, filename string, options ...ChartOption) (*chart.Chart, error) { + ch, err := loadChartFS(bundleFS, filename) + if err != nil { + return nil, err + } + + return enrichChart(ch, options...) +} + +func enrichChart(chart *chart.Chart, options ...ChartOption) (*chart.Chart, error) { + if chart == nil { + return nil, fmt.Errorf("chart can not be nil") + } + for _, f := range options { + f(chart) + } + return chart, nil +} + +var LoadChartFS = loadChartFS + +// loadChartFS loads a chart archive from a filesystem of +// type fs.FS with the provided filename +func loadChartFS(bundleFS fs.FS, filename string) (*chart.Chart, error) { + if filename == "" { + return nil, fmt.Errorf("chart file name was not provided") + } + + tarball, err := fs.ReadFile(bundleFS, filename) + if err != nil { + return nil, fmt.Errorf("reading chart %s; %+v", filename, err) + } + return loader.LoadArchive(bytes.NewBuffer(tarball)) +} diff --git a/internal/shared/util/image/helm_test.go b/internal/shared/util/image/helm_test.go new file mode 100644 index 0000000000..b47162f2e3 --- /dev/null +++ b/internal/shared/util/image/helm_test.go @@ -0,0 +1,682 @@ +package image + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "fmt" + "io/fs" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/containerd/containerd/archive" + goregistry "github.com/google/go-containerregistry/pkg/registry" + "github.com/opencontainers/go-digest" + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/types" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/registry" + + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" +) + +func Test_hasChart(t *testing.T) { + chartTagRef, _, cleanup := setupChartRegistry(t, + mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + ), + ) + defer cleanup() + + imgTagRef, _, shutdown := setupRegistry(t) + defer shutdown() + + type args struct { + srcRef string + contextFunc func(context.Context) (*types.SystemContext, error) + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "returns true when image contains chart", + args: args{ + srcRef: chartTagRef.String(), + contextFunc: buildSourceContextFunc(t, chartTagRef), + }, + want: true, + }, + { + name: "returns false when image is not chart", + args: args{ + srcRef: imgTagRef.String(), + contextFunc: buildSourceContextFunc(t, imgTagRef), + }, + want: false, + }, + } + + ctx := context.Background() + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + srcRef, err := reference.ParseNamed(tc.args.srcRef) + require.NoError(t, err) + + srcImgRef, err := docker.NewReference(srcRef) + require.NoError(t, err) + + sysCtx, err := tc.args.contextFunc(ctx) + require.NoError(t, err) + + imgSrc, err := srcImgRef.NewImageSource(ctx, sysCtx) + require.NoError(t, err) + + img, err := image.FromSource(ctx, sysCtx, imgSrc) + require.NoError(t, err) + + defer func() { + if err := img.Close(); err != nil { + panic(err) + } + }() + + got := hasChart(img) + require.Equal(t, tc.want, got) + }) + } +} + +func Test_pullChart(t *testing.T) { + const myOwner = "myOwner" + myChartName := "testchart-0.1.0.tgz" + testChart := mockHelmChartTgz(t, + []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + ) + + myTagRef, myCanonicalRef, cleanup := setupChartRegistry(t, testChart) + defer cleanup() + + tests := []struct { + name string + ownerID string + srcRef string + cache Cache + contextFunc func(context.Context) (*types.SystemContext, error) + expect func(*testing.T, fs.FS, time.Time) + }{ + { + name: "pull helm chart from OCI registry", + ownerID: myOwner, + srcRef: myTagRef.String(), + cache: &diskCache{ + basePath: t.TempDir(), + filterFunc: func(ctx context.Context, named reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return forceOwnershipRWX(), nil + }, + }, + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, modTime time.Time) { + now := time.Now() + require.LessOrEqual(t, now.Sub(modTime), 3*time.Second, "modified time should less than 3 seconds") + + actualChartData, err := fs.ReadFile(fsys, myChartName) + require.NoError(t, err) + + assert.Equal(t, testChart, actualChartData) + }, + }, + } + + ctx := context.Background() + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + srcRef, err := reference.ParseNamed(tc.srcRef) + require.NoError(t, err) + + srcImgRef, err := docker.NewReference(srcRef) + require.NoError(t, err) + + sysCtx, err := tc.contextFunc(ctx) + require.NoError(t, err) + + imgSrc, err := srcImgRef.NewImageSource(ctx, sysCtx) + require.NoError(t, err) + + fsys, modTime, err := pullChart(ctx, tc.ownerID, srcRef, myCanonicalRef, imgSrc, tc.cache) + require.NotNil(t, tc.expect, "expect function must be defined") + require.NoError(t, err) + + tc.expect(t, fsys, modTime) + + if dc, ok := tc.cache.(*diskCache); ok && dc.basePath != "" { + require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) + } + }) + } +} + +func TestIsValidChart(t *testing.T) { + tt := []struct { + name string + target *chart.Chart + wantErr bool + errMsg string + }{ + { + name: "helm chart with required metadata", + target: &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: "v2", + Name: "sample-chart", + Version: "0.1.2", + }, + }, + wantErr: false, + }, + { + name: "helm chart without name", + target: &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: "v2", + Name: "", + Version: "0.1.2", + }, + }, + wantErr: true, + errMsg: "chart name is required", + }, + { + name: "helm chart with missing version", + target: &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: "v2", + Name: "sample-chart", + Version: "", + }, + }, + wantErr: true, + errMsg: "chart version is required", + }, + { + name: "helm chart with missing metadata", + target: &chart.Chart{ + Metadata: nil, + }, + wantErr: true, + errMsg: "chart metadata is missing", + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := IsValidChart(tc.target) + if tc.wantErr && assert.Error(t, err, "checking valid chart") { + assert.EqualError(t, err, tc.errMsg, "validating chart") + } + }) + } +} + +func TestIsBundleSourceChart(t *testing.T) { + type args struct { + meta *chart.Metadata + files []fileContent + } + type want struct { + value bool + errStr string + } + tt := []struct { + name string + args args + want want + }{ + { + name: "complete helm chart with nil *chart.Metadata", + args: args{ + meta: nil, + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + value: true, + }, + }, + { + name: "complete helm chart", + args: args{ + meta: &chart.Metadata{}, + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + value: true, + }, + }, + { + name: "helm chart without templates", + args: args{ + meta: nil, + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + }, + }, + want: want{ + value: false, + }, + }, + { + name: "helm chart without a Chart.yaml", + args: args{ + meta: nil, + files: []fileContent{ + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + value: false, + errStr: "reading testchart-0.1.0.tgz from fs: loading chart archive: Chart.yaml file is missing", + }, + }, + { + name: "invalid chart archive", + args: args{ + meta: nil, + files: []fileContent{ + { + name: "testchart/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + value: false, + errStr: "reading testchart-0.1.0.tgz from fs: loading chart archive: Chart.yaml file is missing", + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) + got, err := IsBundleSourceChart(chartFS, tc.args.meta) + if tc.want.errStr != "" { + require.Error(t, err, "chart validation error required") + require.EqualError(t, err, tc.want.errStr, "chart error") + } + require.Equal(t, tc.want.value, got, "validata helm chart") + }) + } +} + +func Test_loadChartFS(t *testing.T) { + type args struct { + filename string + files []fileContent + } + type want struct { + name string + version string + errMsg string + } + tests := []struct { + name string + args args + want want + expect func(*chart.Chart, want, error) + }{ + { + name: "empty filename is provided", + args: args{ + filename: "", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + name: "", + errMsg: "chart file name was not provided", + }, + expect: func(chart *chart.Chart, want want, err error) { + require.EqualError(t, err, want.errMsg) + assert.Nil(t, chart, "no chart would be returned") + }, + }, + { + name: "load sample chart", + args: args{ + filename: "testchart-0.1.0.tgz", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + name: "testchart", + version: "0.1.0", + }, + expect: func(chart *chart.Chart, want want, err error) { + require.NoError(t, err, "chart should load successfully") + assert.Equal(t, want.name, chart.Metadata.Name, "verify chart name") + assert.Equal(t, want.version, chart.Metadata.Version, "verify chart version") + }, + }, + { + name: "load nonexistent chart", + args: args{ + filename: "nonexistent-chart-0.1.0.tgz", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + name: "nonexistent-chart", + version: "0.1.0", + }, + expect: func(chart *chart.Chart, want want, err error) { + assert.Nil(t, chart, "chart does not exist on filesystem") + require.Error(t, err, "reading chart nonexistent-chart-0.1.0.tgz; open nonexistent-chart-0.1.0.tgz: no such file or directory") + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) + + got, err := loadChartFS(chartFS, tc.args.filename) + assert.NotNil(t, tc.expect, "validation function") + tc.expect(got, tc.want, err) + }) + } +} + +func TestLoadChartFSWithOptions(t *testing.T) { + type args struct { + filename string + files []fileContent + } + type want struct { + name string + version string + errMsg string + } + tests := []struct { + name string + args args + want want + expect func(*chart.Chart, want, error) + }{ + { + name: "empty filename is provided", + args: args{ + filename: "", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + errMsg: "chart file name was not provided", + }, + expect: func(chart *chart.Chart, want want, err error) { + require.Error(t, err, want.errMsg) + }, + }, + { + name: "load sample chart", + args: args{ + filename: "testchart-0.1.0.tgz", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + name: "testchart", + version: "0.1.0", + }, + expect: func(chart *chart.Chart, want want, err error) { + require.NoError(t, err) + assert.Equal(t, want.name, chart.Metadata.Name, "chart name") + assert.Equal(t, want.version, chart.Metadata.Version, "chart version") + }, + }, + { + name: "load nonexistent chart", + args: args{ + filename: "nonexistent-chart-0.1.0.tgz", + files: []fileContent{ + { + name: "testchart/Chart.yaml", + content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), + }, + { + name: "testchart/templates/deployment.yaml", + content: []byte("kind: Deployment\napiVersion: apps/v1"), + }, + }, + }, + want: want{ + errMsg: "reading chart nonexistent-chart-0.1.0.tgz; open nonexistent-chart-0.1.0.tgz: no such file or directory", + }, + expect: func(chart *chart.Chart, want want, err error) { + require.Error(t, err, want.errMsg) + assert.Nil(t, chart) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) + got, err := LoadChartFSWithOptions(chartFS, tc.args.filename, WithInstallNamespace("metrics-server-system")) + require.NotNil(t, tc.expect) + tc.expect(got, tc.want, err) + }) + } +} + +func Test_enrichChart(t *testing.T) { + type args struct { + chart *chart.Chart + options []ChartOption + } + tests := []struct { + name string + args args + want *chart.Chart + wantErr bool + }{ + { + name: "enrich empty chart object", + args: args{ + chart: nil, + options: []ChartOption{ + WithInstallNamespace("test-namespace-system"), + }, + }, + wantErr: true, + want: nil, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := enrichChart(tc.args.chart, tc.args.options...) + if (err != nil) != tc.wantErr { + t.Errorf("enrichChart() error = %v, wantErr %v", err, tc.wantErr) + return + } + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("enrichChart() = %v, want %v", got, tc.want) + } + }) + } +} + +func setupChartRegistry(t *testing.T, chart []byte) (reference.NamedTagged, reference.Canonical, func()) { + server := httptest.NewServer(goregistry.New()) + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + clientOpts := []registry.ClientOption{ + registry.ClientOptDebug(true), + registry.ClientOptEnableCache(true), + } + client, err := registry.NewClient(clientOpts...) + require.NoError(t, err) + + testCreationTime := "2020-09-22T22:04:05Z" + ref := fmt.Sprintf("%s/testrepo/testchart:%s", serverURL.Host, "0.1.0") + result, err := client.Push(chart, ref, registry.PushOptCreationTime(testCreationTime)) + require.NoError(t, err) + + imageTagRef, err := newReference(serverURL.Host, "testrepo/testchart", "0.1.0") + require.NoError(t, err) + + imageDigestRef, err := reference.WithDigest( + reference.TrimNamed(imageTagRef), + digest.Digest(result.Manifest.Digest), + ) + require.NoError(t, err) + + return imageTagRef, imageDigestRef, func() { + server.Close() + } +} + +type fileContent struct { + name string + content []byte +} + +func mockHelmChartTgz(t *testing.T, contents []fileContent) []byte { + require.NotEmpty(t, contents, "chart content required") + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + // Add files to the chart archive + for _, file := range contents { + require.NoError(t, tw.WriteHeader(&tar.Header{ + Name: file.name, + Mode: 0600, + Size: int64(len(file.content)), + })) + _, _ = tw.Write(file.content) + } + + require.NoError(t, tw.Close()) + + var gzBuf bytes.Buffer + gz := gzip.NewWriter(&gzBuf) + _, err := gz.Write(buf.Bytes()) + require.NoError(t, err) + require.NoError(t, gz.Close()) + + return gzBuf.Bytes() +} + +func createTempFS(t *testing.T, data []byte) (fs.FS, error) { + require.NotEmpty(t, data, "chart data") + tmpDir, _ := os.MkdirTemp(t.TempDir(), "bundlefs-") + if len(data) == 0 { + return os.DirFS(tmpDir), nil + } + + dest, err := os.Create(filepath.Join(tmpDir, "testchart-0.1.0.tgz")) + if err != nil { + return nil, err + } + defer dest.Close() + + if _, err := dest.Write(data); err != nil { + return nil, err + } + + return os.DirFS(tmpDir), nil +} diff --git a/internal/shared/util/image/mocks.go b/internal/shared/util/image/mocks.go new file mode 100644 index 0000000000..82e8226e26 --- /dev/null +++ b/internal/shared/util/image/mocks.go @@ -0,0 +1,61 @@ +package image + +import ( + "context" + "io/fs" + "iter" + "time" + + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "go.podman.io/image/v5/docker/reference" +) + +var _ Puller = (*MockPuller)(nil) + +// MockPuller is a utility for mocking out a Puller interface +type MockPuller struct { + ImageFS fs.FS + Ref reference.Canonical + ModTime time.Time + Error error +} + +func (ms *MockPuller) Pull(_ context.Context, _, _ string, _ Cache) (fs.FS, reference.Canonical, time.Time, error) { + if ms.Error != nil { + return nil, nil, time.Time{}, ms.Error + } + + return ms.ImageFS, ms.Ref, ms.ModTime, nil +} + +var _ Cache = (*MockCache)(nil) + +type MockCache struct { + FetchFS fs.FS + FetchModTime time.Time + FetchError error + + StoreFS fs.FS + StoreModTime time.Time + StoreError error + + DeleteErr error + + GarbageCollectError error +} + +func (m MockCache) Fetch(_ context.Context, _ string, _ reference.Canonical) (fs.FS, time.Time, error) { + return m.FetchFS, m.FetchModTime, m.FetchError +} + +func (m MockCache) Store(_ context.Context, _ string, _ reference.Named, _ reference.Canonical, _ ocispecv1.Image, _ iter.Seq[LayerData]) (fs.FS, time.Time, error) { + return m.StoreFS, m.StoreModTime, m.StoreError +} + +func (m MockCache) Delete(_ context.Context, _ string) error { + return m.DeleteErr +} + +func (m MockCache) GarbageCollect(_ context.Context, _ string, _ reference.Canonical) error { + return m.GarbageCollectError +} diff --git a/internal/shared/util/image/pull.go b/internal/shared/util/image/pull.go new file mode 100644 index 0000000000..1fff90809b --- /dev/null +++ b/internal/shared/util/image/pull.go @@ -0,0 +1,282 @@ +package image + +import ( + "context" + "errors" + "fmt" + "io/fs" + "iter" + "os" + "time" + + "github.com/go-logr/logr" + "go.podman.io/image/v5/copy" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/oci/layout" + "go.podman.io/image/v5/pkg/blobinfocache/none" + "go.podman.io/image/v5/pkg/compression" + "go.podman.io/image/v5/pkg/sysregistriesv2" + "go.podman.io/image/v5/signature" + "go.podman.io/image/v5/types" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/operator-framework/operator-controller/internal/operator-controller/features" + "github.com/operator-framework/operator-controller/internal/shared/util/http" +) + +type Puller interface { + Pull(context.Context, string, string, Cache) (fs.FS, reference.Canonical, time.Time, error) +} + +var insecurePolicy = []byte(`{"default":[{"type":"insecureAcceptAnything"}]}`) + +type ContainersImagePuller struct { + SourceCtxFunc func(context.Context) (*types.SystemContext, error) +} + +func (p *ContainersImagePuller) Pull(ctx context.Context, ownerID string, ref string, cache Cache) (fs.FS, reference.Canonical, time.Time, error) { + srcCtx, err := p.SourceCtxFunc(ctx) + if err != nil { + return nil, nil, time.Time{}, err + } + + dockerRef, err := reference.ParseNamed(ref) + if err != nil { + return nil, nil, time.Time{}, reconcile.TerminalError(fmt.Errorf("error parsing image reference %q: %w", ref, err)) + } + + l := log.FromContext(ctx, "ref", dockerRef.String()) + ctx = log.IntoContext(ctx, l) + + fsys, canonicalRef, modTime, err := p.pull(ctx, ownerID, dockerRef, cache, srcCtx) + if err != nil { + // Log any CertificateVerificationErrors, and log Docker Certificates if necessary + if http.LogCertificateVerificationError(err, l) { + http.LogDockerCertificates(srcCtx.DockerCertPath, l) + } + return nil, nil, time.Time{}, err + } + return fsys, canonicalRef, modTime, nil +} + +func (p *ContainersImagePuller) pull(ctx context.Context, ownerID string, dockerRef reference.Named, cache Cache, srcCtx *types.SystemContext) (fs.FS, reference.Canonical, time.Time, error) { + l := log.FromContext(ctx) + + dockerImgRef, err := docker.NewReference(dockerRef) + if err != nil { + return nil, nil, time.Time{}, reconcile.TerminalError(fmt.Errorf("error creating reference: %w", err)) + } + + // Reload registries cache in case of configuration update + sysregistriesv2.InvalidateCache() + + ////////////////////////////////////////////////////// + // + // Resolve a canonical reference for the image. + // + ////////////////////////////////////////////////////// + canonicalRef, err := resolveCanonicalRef(ctx, dockerImgRef, srcCtx) + if err != nil { + return nil, nil, time.Time{}, err + } + + l = l.WithValues("digest", canonicalRef.Digest().String()) + ctx = log.IntoContext(ctx, l) + + /////////////////////////////////////////////////////// + // + // Check if the cache has already applied the + // canonical keep. If so, we're done. + // + /////////////////////////////////////////////////////// + fsys, modTime, err := cache.Fetch(ctx, ownerID, canonicalRef) + if err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error checking cache for existing content: %w", err) + } + if fsys != nil { + return fsys, canonicalRef, modTime, nil + } + + ////////////////////////////////////////////////////// + // + // Create an OCI layout reference for the destination, + // where we will temporarily store the image in order + // to unpack it. + // + // We use the OCI layout as a temporary storage because + // copy.Image can concurrently pull all the layers. + // + ////////////////////////////////////////////////////// + layoutDir, err := os.MkdirTemp("", fmt.Sprintf("oci-layout-%s-", ownerID)) + if err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error creating temporary directory: %w", err) + } + defer func() { + if err := os.RemoveAll(layoutDir); err != nil { + l.Error(err, "error removing temporary OCI layout directory") + } + }() + + layoutImgRef, err := layout.NewReference(layoutDir, canonicalRef.String()) + if err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error creating reference: %w", err) + } + + ////////////////////////////////////////////////////// + // + // Load an image signature policy and build + // a policy context for the image pull. + // + ////////////////////////////////////////////////////// + policyContext, err := loadPolicyContext(srcCtx, l) + if err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error loading policy context: %w", err) + } + defer func() { + if err := policyContext.Destroy(); err != nil { + l.Error(err, "error destroying policy context") + } + }() + + ////////////////////////////////////////////////////// + // + // Pull the image from the source to the destination + // + ////////////////////////////////////////////////////// + if _, err := copy.Image(ctx, policyContext, layoutImgRef, dockerImgRef, ©.Options{ + SourceCtx: srcCtx, + // We use the OCI layout as a temporary storage and + // pushing signatures for OCI images is not supported + // so we remove the source signatures when copying. + // Signature validation will still be performed + // accordingly to a provided policy context. + RemoveSignatures: true, + }); err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error copying image: %w", err) + } + l.Info("pulled image") + + ////////////////////////////////////////////////////// + // + // Mount the image we just pulled + // + ////////////////////////////////////////////////////// + fsys, modTime, err = p.applyImage(ctx, ownerID, dockerRef, canonicalRef, layoutImgRef, cache, srcCtx) + if err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error applying image: %w", err) + } + + ///////////////////////////////////////////////////////////// + // + // Clean up any images from the cache that we no longer need. + // + ///////////////////////////////////////////////////////////// + if err := cache.GarbageCollect(ctx, ownerID, canonicalRef); err != nil { + return nil, nil, time.Time{}, fmt.Errorf("error deleting old images: %w", err) + } + return fsys, canonicalRef, modTime, nil +} + +func resolveCanonicalRef(ctx context.Context, imgRef types.ImageReference, srcCtx *types.SystemContext) (reference.Canonical, error) { + if canonicalRef, ok := imgRef.DockerReference().(reference.Canonical); ok { + return canonicalRef, nil + } + + imgSrc, err := imgRef.NewImageSource(ctx, srcCtx) + if err != nil { + return nil, fmt.Errorf("error creating image source: %w", err) + } + defer imgSrc.Close() + + manifestBlob, _, err := imgSrc.GetManifest(ctx, nil) + if err != nil { + return nil, fmt.Errorf("error getting manifest: %w", err) + } + imgDigest, err := manifest.Digest(manifestBlob) + if err != nil { + return nil, fmt.Errorf("error getting digest of manifest: %w", err) + } + canonicalRef, err := reference.WithDigest(reference.TrimNamed(imgRef.DockerReference()), imgDigest) + if err != nil { + return nil, fmt.Errorf("error creating canonical reference: %w", err) + } + return canonicalRef, nil +} + +func (p *ContainersImagePuller) applyImage(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, srcImgRef types.ImageReference, cache Cache, sourceContext *types.SystemContext) (fs.FS, time.Time, error) { + imgSrc, err := srcImgRef.NewImageSource(ctx, sourceContext) + if err != nil { + return nil, time.Time{}, fmt.Errorf("error creating image source: %w", err) + } + img, err := image.FromSource(ctx, sourceContext, imgSrc) + if err != nil { + return nil, time.Time{}, errors.Join( + fmt.Errorf("error reading image: %w", err), + imgSrc.Close(), + ) + } + defer func() { + if err := img.Close(); err != nil { + panic(err) + } + }() + + if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) { + if hasChart(img) { + return pullChart(ctx, ownerID, srcRef, canonicalRef, imgSrc, cache) + } + } + + ociImg, err := img.OCIConfig(ctx) + if err != nil { + return nil, time.Time{}, err + } + + layerIter := iter.Seq[LayerData](func(yield func(LayerData) bool) { + for i, layerInfo := range img.LayerInfos() { + ld := LayerData{Index: i, MediaType: layerInfo.MediaType} + layerReader, _, err := imgSrc.GetBlob(ctx, layerInfo, none.NoCache) + if err != nil { + ld.Err = fmt.Errorf("error getting layer blob reader: %w", err) + if !yield(ld) { + return + } + } + defer layerReader.Close() + + decompressed, _, err := compression.AutoDecompress(layerReader) + if err != nil { + ld.Err = fmt.Errorf("error decompressing layer: %w", err) + if !yield(ld) { + return + } + } + defer decompressed.Close() + + ld.Reader = decompressed + if !yield(ld) { + return + } + } + }) + + return cache.Store(ctx, ownerID, srcRef, canonicalRef, *ociImg, layerIter) +} + +func loadPolicyContext(sourceContext *types.SystemContext, l logr.Logger) (*signature.PolicyContext, error) { + policy, err := signature.DefaultPolicy(sourceContext) + // TODO: there are security implications to silently moving to an insecure policy + // tracking issue: https://github.com/operator-framework/operator-controller/issues/1622 + if err != nil { + l.Info("no default policy found, using insecure policy") + policy, err = signature.NewPolicyFromBytes(insecurePolicy) + } + if err != nil { + return nil, fmt.Errorf("error loading signature policy: %w", err) + } + return signature.NewPolicyContext(policy) +} diff --git a/internal/shared/util/image/pull_test.go b/internal/shared/util/image/pull_test.go new file mode 100644 index 0000000000..45a04062f5 --- /dev/null +++ b/internal/shared/util/image/pull_test.go @@ -0,0 +1,305 @@ +package image + +import ( + "context" + "errors" + "fmt" + "io/fs" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + "testing/fstest" + "time" + + "github.com/BurntSushi/toml" + "github.com/containerd/containerd/archive" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/registry" + "github.com/opencontainers/go-digest" + ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/pkg/sysregistriesv2" + "go.podman.io/image/v5/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" +) + +const ( + testFileName string = "test-file" + testFileContents string = "test-content" +) + +func TestContainersImagePuller_Pull(t *testing.T) { + const myOwner = "myOwner" + myTagRef, myCanonicalRef, shutdown := setupRegistry(t) + defer shutdown() + + myModTime := time.Date(1985, 10, 25, 7, 53, 0, 0, time.FixedZone("PDT", -8*60*60)) + defaultContextFunc := func(context.Context) (*types.SystemContext, error) { return &types.SystemContext{}, nil } + + testCases := []struct { + name string + ownerID string + srcRef string + cache Cache + contextFunc func(context.Context) (*types.SystemContext, error) + expect func(*testing.T, fs.FS, reference.Canonical, time.Time, error) + }{ + { + name: "returns terminal error for invalid reference", + ownerID: myOwner, + srcRef: "invalid-src-ref", + contextFunc: defaultContextFunc, + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "error parsing image reference") + require.ErrorIs(t, err, reconcile.TerminalError(nil)) + }, + }, + { + name: "returns terminal error if reference lacks tag or digest", + ownerID: myOwner, + srcRef: reference.TrimNamed(myTagRef).String(), + contextFunc: defaultContextFunc, + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "error creating reference") + require.ErrorIs(t, err, reconcile.TerminalError(nil)) + }, + }, + { + name: "returns error if failure getting SystemContext", + ownerID: myOwner, + srcRef: myCanonicalRef.String(), + contextFunc: func(ctx context.Context) (*types.SystemContext, error) { + return nil, errors.New("sourcecontextfunc error") + }, + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "sourcecontextfunc error") + }, + }, + { + name: "returns error if failure connecting to reference's registry", + ownerID: myOwner, + srcRef: myTagRef.String() + "-non-existent", + contextFunc: defaultContextFunc, + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "pinging container registry") + }, + }, + { + name: "returns error if tag ref is not found", + ownerID: myOwner, + srcRef: myTagRef.String() + "-non-existent", + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "manifest unknown") + }, + }, + { + name: "return error if cache fetch fails", + ownerID: myOwner, + srcRef: myCanonicalRef.String(), + cache: MockCache{FetchError: errors.New("fetch error")}, + contextFunc: defaultContextFunc, + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "fetch error") + }, + }, + { + name: "return canonical ref's data from cache, if present", + ownerID: myOwner, + srcRef: myCanonicalRef.String(), + cache: MockCache{ + FetchFS: fstest.MapFS{ + testFileName: &fstest.MapFile{Data: []byte(testFileContents)}, + }, + FetchModTime: myModTime, + }, + contextFunc: buildSourceContextFunc(t, myCanonicalRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.NoError(t, err) + actualFileData, err := fs.ReadFile(fsys, testFileName) + require.NoError(t, err) + assert.Equal(t, testFileContents, string(actualFileData)) + + assert.Equal(t, myCanonicalRef.String(), canonical.String()) + assert.Equal(t, myModTime, modTime) + }, + }, + { + name: "return tag ref's data from cache, if present", + ownerID: myOwner, + srcRef: myTagRef.String(), + cache: MockCache{ + FetchFS: fstest.MapFS{ + testFileName: &fstest.MapFile{Data: []byte(testFileContents)}, + }, + FetchModTime: myModTime, + }, + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.NoError(t, err) + actualFileData, err := fs.ReadFile(fsys, testFileName) + require.NoError(t, err) + assert.Equal(t, testFileContents, string(actualFileData)) + + assert.Equal(t, myCanonicalRef.String(), canonical.String()) + assert.Equal(t, myModTime, modTime) + }, + }, + { + name: "returns error if failure storing content in cache", + ownerID: myOwner, + srcRef: myCanonicalRef.String(), + cache: MockCache{ + StoreError: errors.New("store error"), + }, + contextFunc: buildSourceContextFunc(t, myCanonicalRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.ErrorContains(t, err, "store error") + }, + }, + { + name: "returns stored data upon pull success", + ownerID: myOwner, + srcRef: myTagRef.String(), + cache: MockCache{ + StoreFS: fstest.MapFS{ + testFileName: &fstest.MapFile{Data: []byte(testFileContents)}, + }, + StoreModTime: myModTime, + }, + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.NoError(t, err) + + actualFileData, err := fs.ReadFile(fsys, testFileName) + require.NoError(t, err) + assert.Equal(t, testFileContents, string(actualFileData)) + + assert.Equal(t, myCanonicalRef.String(), canonical.String()) + assert.Equal(t, myModTime, modTime) + }, + }, + { + name: "returns error if cache garbage collection fails", + ownerID: myOwner, + srcRef: myTagRef.String(), + cache: MockCache{ + StoreFS: fstest.MapFS{ + testFileName: &fstest.MapFile{Data: []byte(testFileContents)}, + }, + StoreModTime: myModTime, + GarbageCollectError: errors.New("garbage collect error"), + }, + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + assert.Nil(t, fsys) + assert.Nil(t, canonical) + assert.Zero(t, modTime) + assert.ErrorContains(t, err, "garbage collect error") + }, + }, + { + name: "succeeds storing actual image contents using real cache", + ownerID: myOwner, + srcRef: myTagRef.String(), + cache: &diskCache{ + basePath: t.TempDir(), + filterFunc: func(ctx context.Context, named reference.Named, image ocispecv1.Image) (archive.Filter, error) { + return forceOwnershipRWX(), nil + }, + }, + contextFunc: buildSourceContextFunc(t, myTagRef), + expect: func(t *testing.T, fsys fs.FS, canonical reference.Canonical, modTime time.Time, err error) { + require.NoError(t, err) + actualFileData, err := fs.ReadFile(fsys, testFileName) + require.NoError(t, err) + assert.Equal(t, testFileContents, string(actualFileData)) + assert.Equal(t, myCanonicalRef.String(), canonical.String()) + + // Don't assert modTime since it is an implementation detail + // of the cache, which we are not testing here. + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + puller := ContainersImagePuller{ + SourceCtxFunc: tc.contextFunc, + } + fsys, canonicalRef, modTime, err := puller.Pull(context.Background(), tc.ownerID, tc.srcRef, tc.cache) + require.NotNil(t, tc.expect, "expect function must be defined") + tc.expect(t, fsys, canonicalRef, modTime, err) + + if dc, ok := tc.cache.(*diskCache); ok && dc.basePath != "" { + require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) + } + }) + } +} + +func setupRegistry(t *testing.T) (reference.NamedTagged, reference.Canonical, func()) { + server := httptest.NewServer(registry.New()) + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + // Generate an image with file contents + img, err := crane.Image(map[string][]byte{testFileName: []byte(testFileContents)}) + require.NoError(t, err) + + imageTagRef, err := newReference(serverURL.Host, "test-repo/test-image", "test-tag") + require.NoError(t, err) + + imgDigest, err := img.Digest() + require.NoError(t, err) + + imageDigestRef, err := reference.WithDigest(reference.TrimNamed(imageTagRef), digest.Digest(imgDigest.String())) + require.NoError(t, err) + + require.NoError(t, crane.Push(img, imageTagRef.String())) + + cleanup := func() { + server.Close() + } + return imageTagRef, imageDigestRef, cleanup +} + +func newReference(host, repo, tag string) (reference.NamedTagged, error) { + ref, err := reference.ParseNamed(fmt.Sprintf("%s/%s", host, repo)) + if err != nil { + return nil, err + } + return reference.WithTag(ref, tag) +} + +func buildSourceContextFunc(t *testing.T, ref reference.Named) func(context.Context) (*types.SystemContext, error) { + return func(ctx context.Context) (*types.SystemContext, error) { + // Build a containers/image context that allows pulling from the test registry insecurely + registriesConf := sysregistriesv2.V2RegistriesConf{Registries: []sysregistriesv2.Registry{ + { + Prefix: reference.Domain(ref), + Endpoint: sysregistriesv2.Endpoint{ + Location: reference.Domain(ref), + Insecure: true, + }, + }, + }} + configDir := t.TempDir() + registriesConfPath := filepath.Join(configDir, "registries.conf") + f, err := os.Create(registriesConfPath) + require.NoError(t, err) + + enc := toml.NewEncoder(f) + require.NoError(t, enc.Encode(registriesConf)) + require.NoError(t, f.Close()) + + return &types.SystemContext{ + SystemRegistriesConfPath: registriesConfPath, + }, nil + } +} diff --git a/internal/shared/util/pullsecretcache/pullsecretcache.go b/internal/shared/util/pullsecretcache/pullsecretcache.go new file mode 100644 index 0000000000..910b4b50b8 --- /dev/null +++ b/internal/shared/util/pullsecretcache/pullsecretcache.go @@ -0,0 +1,40 @@ +package pullsecretcache + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/cache" +) + +func SetupPullSecretCache(cacheOptions *cache.Options, globalPullSecretKey *types.NamespacedName, saKey types.NamespacedName) error { + cacheOptions.ByObject[&corev1.ServiceAccount{}] = cache.ByObject{ + Namespaces: map[string]cache.Config{ + saKey.Namespace: { + LabelSelector: labels.Everything(), + FieldSelector: fields.SelectorFromSet(map[string]string{ + "metadata.name": saKey.Name, + }), + }, + }, + } + + secretCache := cache.ByObject{} + secretCache.Namespaces = make(map[string]cache.Config, 2) + secretCache.Namespaces[saKey.Namespace] = cache.Config{ + LabelSelector: labels.Everything(), + FieldSelector: fields.Everything(), + } + if globalPullSecretKey != nil && globalPullSecretKey.Namespace != saKey.Namespace { + secretCache.Namespaces[globalPullSecretKey.Namespace] = cache.Config{ + LabelSelector: labels.Everything(), + FieldSelector: fields.SelectorFromSet(map[string]string{ + "metadata.name": globalPullSecretKey.Name, + }), + } + } + cacheOptions.ByObject[&corev1.Secret{}] = secretCache + + return nil +} diff --git a/internal/shared/util/sa/serviceaccount.go b/internal/shared/util/sa/serviceaccount.go new file mode 100644 index 0000000000..f668c859e8 --- /dev/null +++ b/internal/shared/util/sa/serviceaccount.go @@ -0,0 +1,51 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sa + +import ( + "fmt" + "os" + "strings" + + "github.com/golang-jwt/jwt/v5" + k8stypes "k8s.io/apimachinery/pkg/types" +) + +// Returns nameaspce/serviceaccount name +func GetServiceAccount() (k8stypes.NamespacedName, error) { + return getServiceAccountInternal(os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")) +} + +func getServiceAccountInternal(data []byte, err error) (k8stypes.NamespacedName, error) { + if err != nil { + return k8stypes.NamespacedName{}, err + } + // Not verifying the token, we just want to extract the subject + token, _, err := jwt.NewParser([]jwt.ParserOption{}...).ParseUnverified(string(data), jwt.MapClaims{}) + if err != nil { + return k8stypes.NamespacedName{}, err + } + subject, err := token.Claims.GetSubject() + if err != nil { + return k8stypes.NamespacedName{}, err + } + subjects := strings.Split(subject, ":") + if len(subjects) != 4 || subjects[2] == "" || subjects[3] == "" { + return k8stypes.NamespacedName{}, fmt.Errorf("badly formatted subject: %s", subject) + } + return k8stypes.NamespacedName{Namespace: subjects[2], Name: subjects[3]}, nil +} diff --git a/internal/shared/util/sa/serviceaccount_test.go b/internal/shared/util/sa/serviceaccount_test.go new file mode 100644 index 0000000000..b18663e662 --- /dev/null +++ b/internal/shared/util/sa/serviceaccount_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sa + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + // taken from a kind run + goodSa = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdyM3VIbkJ0VlRQVy1uWWlsSVFCV2pfQmdTS0RIdjZHNDBVT1hDSVFtZmcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzgwNTEwMjAwLCJpYXQiOjE3NDg5NzQyMDAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiNTQ2OThmZGYtNzg4NC00YzhkLWI5NzctYTg4YThiYmY3ODQxIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJvbG12MS1zeXN0ZW0iLCJub2RlIjp7Im5hbWUiOiJvcGVyYXRvci1jb250cm9sbGVyLWUyZS1jb250cm9sLXBsYW5lIiwidWlkIjoiZWY0YjdkNGQtZmUxZi00MThkLWIyZDAtM2ZmYWJmMWQ0ZDI3In0sInBvZCI6eyJuYW1lIjoib3BlcmF0b3ItY29udHJvbGxlci1jb250cm9sbGVyLW1hbmFnZXItNjU3Njg1ZGNkYy01cTZ0dCIsInVpZCI6IjE4MmFkNTkxLWUzYTktNDMyNC1hMjk4LTg0NzIxY2Q0OTAzYSJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoib3BlcmF0b3ItY29udHJvbGxlci1jb250cm9sbGVyLW1hbmFnZXIiLCJ1aWQiOiI3MDliZTA4OS00OTI1LTQ2NjYtYjA1Ny1iYWMyNmVmYWJjMGIifSwid2FybmFmdGVyIjoxNzQ4OTc3ODA3fSwibmJmIjoxNzQ4OTc0MjAwLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6b2xtdjEtc3lzdGVtOm9wZXJhdG9yLWNvbnRyb2xsZXItY29udHJvbGxlci1tYW5hZ2VyIn0.OjExhuNHdMZjdGwDXM0bWQnJKcfLNpEJ2S47BzlAa560uNw8EwMItlfpG970umQBbVPWhyhUBFimUD5XmXWAlrNvhFwpOLXw2W978Obs1mna5JWcHliC6IkwrOMCh5k9XReQ9-KBdw36QY1G2om77-7mNtPNPg9lg5TQaLuNGrIhX9EC_tucbflXSvB-SA243J_X004W4HkJirt6vVH5FoRg-MDohXm0C4bhTeaXfOtTW6fwsnpomCKso7apu_eOG9E2h8CXXYKhZg4Jrank_Ata8J1lANh06FuxRQK-vwqFrW3_9rscGxweM5CbeicZFOc6MDIuYtgR515YTHPbUA" // notsecret + badSa1 = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdyM3VIbkJ0VlRQVy1uWWlsSVFCV2pfQmdTS0RIdjZHNDBVT1hDSVFtZmcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzgwNTEwMjAwLCJpYXQiOjE3NDg5NzQyMDAsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiNTQ2OThmZGYtNzg4NC00YzhkLWI5NzctYTg4YThiYmY3ODQxIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJvbG12MS1zeXN0ZW0iLCJub2RlIjp7Im5hbWUiOiJvcGVyYXRvci1jb250cm9sbGVyLWUyZS1jb250cm9sLXBsYW5lIiwidWlkIjoiZWY0YjdkNGQtZmUxZi00MThkLWIyZDAtM2ZmYWJmMWQ0ZDI3In0sInBvZCI6eyJuYW1lIjoib3BlcmF0b3ItY29udHJvbGxlci1jb250cm9sbGVyLW1hbmFnZXItNjU3Njg1ZGNkYy01cTZ0dCIsInVpZCI6IjE4MmFkNTkxLWUzYTktNDMyNC1hMjk4LTg0NzIxY2Q0OTAzYSJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoib3BlcmF0b3ItY29udHJvbGxlci1jb250cm9sbGVyLW1hbmFnZXIiLCJ1aWQiOiI3MDliZTA4OS00OTI1LTQ2NjYtYjA1Ny1iYWMyNmVmYWJjMGIifSwid2FybmFmdGVyIjoxNzQ4OTc3ODA3fSwibmJmIjoxNzQ4OTc0MjAwLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnRzOm9sbXYxLXN5c3RlbSJ9.OjExhuNHdMZjdGwDXM0bWQnJKcfLNpEJ2S47BzlAa560uNw8EwMItlfpG970umQBbVPWhyhUBFimUD5XmXWAlrNvhFwpOLXw2W978Obs1mna5JWcHliC6IkwrOMCh5k9XReQ9-KBdw36QY1G2om77-7mNtPNPg9lg5TQaLuNGrIhX9EC_tucbflXSvB-SA243J_X004W4HkJirt6vVH5FoRg-MDohXm0C4bhTeaXfOtTW6fwsnpomCKso7apu_eOG9E2h8CXXYKhZg4Jrank_Ata8J1lANh06FuxRQK-vwqFrW3_9rscGxweM5CbeicZFOc6MDIuYtgR515YTHPbUA" // notsecret + badSa2 = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdyM3VIbkJ0VlRQVy1uWWlsSVFCV2pfQmdTS0RIdjZHNDBVT1hDSVFtZmcifQ" // notsecret +) + +func TestGetServiceAccount(t *testing.T) { + nn, err := getServiceAccountInternal([]byte(goodSa), nil) + require.NoError(t, err) + require.Equal(t, "olmv1-system", nn.Namespace) + require.Equal(t, "operator-controller-controller-manager", nn.Name) + + _, err = getServiceAccountInternal([]byte{}, fmt.Errorf("this is a test error")) + require.ErrorContains(t, err, "this is a test") + + // Modified the subject to be invalid + _, err = getServiceAccountInternal([]byte(badSa1), nil) + require.ErrorContains(t, err, "badly formatted subject") + + // Only includes a header + _, err = getServiceAccountInternal([]byte(badSa2), nil) + require.ErrorContains(t, err, "token is malformed") +} diff --git a/internal/shared/util/testutils/artifacts.go b/internal/shared/util/testutils/artifacts.go new file mode 100644 index 0000000000..6bda44b673 --- /dev/null +++ b/internal/shared/util/testutils/artifacts.go @@ -0,0 +1,186 @@ +package testutils + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + kubeclient "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/utils/env" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +// CollectTestArtifacts gets all the artifacts from the test run and saves them to the artifact path. +// Currently, it saves: +// - clusterextensions +// - clusterextensionrevisions +// - pods logs +// - deployments +// - catalogsources +func CollectTestArtifacts(t *testing.T, artifactName string, c client.Client, cfg *rest.Config) { + basePath := env.GetString("ARTIFACT_PATH", "") + if basePath == "" { + return + } + + kubeClient, err := kubeclient.NewForConfig(cfg) + require.NoError(t, err) + + // sanitize the artifact name for use as a directory name + testName := strings.ReplaceAll(strings.ToLower(t.Name()), " ", "-") + // Get the test description and sanitize it for use as a directory name + artifactPath := filepath.Join(basePath, artifactName, fmt.Sprint(time.Now().UnixNano()), testName) + + // Create the full artifact path + err = os.MkdirAll(artifactPath, 0755) + require.NoError(t, err) + + // Get all namespaces + namespaces := corev1.NamespaceList{} + if err := c.List(context.Background(), &namespaces); err != nil { + fmt.Printf("Failed to list namespaces: %v", err) + } + + // get all cluster extensions save them to the artifact path. + clusterExtensions := ocv1.ClusterExtensionList{} + if err := c.List(context.Background(), &clusterExtensions); err != nil { + fmt.Printf("Failed to list cluster extensions: %v", err) + } + for _, clusterExtension := range clusterExtensions.Items { + // Save cluster extension to artifact path + clusterExtensionYaml, err := yaml.Marshal(clusterExtension) + if err != nil { + fmt.Printf("Failed to marshal cluster extension: %v", err) + continue + } + if err := os.WriteFile(filepath.Join(artifactPath, clusterExtension.Name+"-clusterextension.yaml"), clusterExtensionYaml, 0600); err != nil { + fmt.Printf("Failed to write cluster extension to file: %v", err) + } + } + + // get all cluster extension revisions save them to the artifact path. + clusterExtensionRevisions := ocv1.ClusterExtensionRevisionList{} + if err := c.List(context.Background(), &clusterExtensionRevisions); err != nil { + fmt.Printf("Failed to list cluster extensions: %v", err) + } + for _, cer := range clusterExtensionRevisions.Items { + // Save cluster extension to artifact path + clusterExtensionYaml, err := yaml.Marshal(cer) + if err != nil { + fmt.Printf("Failed to marshal cluster extension: %v", err) + continue + } + if err := os.WriteFile(filepath.Join(artifactPath, cer.Name+"-clusterextensionrevision.yaml"), clusterExtensionYaml, 0600); err != nil { + fmt.Printf("Failed to write cluster extension to file: %v", err) + } + } + + // get all catalogsources save them to the artifact path. + catalogsources := ocv1.ClusterCatalogList{} + if err := c.List(context.Background(), &catalogsources, client.InNamespace("")); err != nil { + fmt.Printf("Failed to list catalogsources: %v", err) + } + for _, catalogsource := range catalogsources.Items { + // Save catalogsource to artifact path + catalogsourceYaml, err := yaml.Marshal(catalogsource) + if err != nil { + fmt.Printf("Failed to marshal catalogsource: %v", err) + continue + } + if err := os.WriteFile(filepath.Join(artifactPath, catalogsource.Name+"-catalogsource.yaml"), catalogsourceYaml, 0600); err != nil { + fmt.Printf("Failed to write catalogsource to file: %v", err) + } + } + + for _, namespace := range namespaces.Items { + // let's ignore kube-* namespaces. + if strings.Contains(namespace.Name, "kube-") { + continue + } + + namespacedArtifactPath := filepath.Join(artifactPath, namespace.Name) + if err := os.Mkdir(namespacedArtifactPath, 0755); err != nil { + fmt.Printf("Failed to create namespaced artifact path: %v", err) + continue + } + + // get all deployments in the namespace and save them to the artifact path. + deployments := appsv1.DeploymentList{} + if err := c.List(context.Background(), &deployments, client.InNamespace(namespace.Name)); err != nil { + fmt.Printf("Failed to list deployments %v in namespace: %q", err, namespace.Name) + continue + } + + for _, deployment := range deployments.Items { + // Save deployment to artifact path + deploymentYaml, err := yaml.Marshal(deployment) + if err != nil { + fmt.Printf("Failed to marshal deployment: %v", err) + continue + } + if err := os.WriteFile(filepath.Join(namespacedArtifactPath, deployment.Name+"-deployment.yaml"), deploymentYaml, 0600); err != nil { + fmt.Printf("Failed to write deployment to file: %v", err) + } + } + + // Get secrets in all namespaces + secrets := corev1.SecretList{} + if err := c.List(context.Background(), &secrets, client.InNamespace(namespace.Name)); err != nil { + fmt.Printf("Failed to list secrets %v in namespace: %q", err, namespace.Name) + } + for _, secret := range secrets.Items { + // Save secret to artifact path + secretYaml, err := yaml.Marshal(secret) + if err != nil { + fmt.Printf("Failed to marshal secret: %v", err) + continue + } + if err := os.WriteFile(filepath.Join(namespacedArtifactPath, secret.Name+"-secret.yaml"), secretYaml, 0600); err != nil { + fmt.Printf("Failed to write secret to file: %v", err) + } + } + + // Get logs from all pods in all namespaces + pods := corev1.PodList{} + if err := c.List(context.Background(), &pods, client.InNamespace(namespace.Name)); err != nil { + fmt.Printf("Failed to list pods %v in namespace: %q", err, namespace.Name) + } + for _, pod := range pods.Items { + if pod.Status.Phase != corev1.PodRunning && pod.Status.Phase != corev1.PodSucceeded && pod.Status.Phase != corev1.PodFailed { + continue + } + for _, container := range pod.Spec.Containers { + logs, err := kubeClient.CoreV1().Pods(namespace.Name).GetLogs(pod.Name, &corev1.PodLogOptions{Container: container.Name}).Stream(context.Background()) + if err != nil { + fmt.Printf("Failed to get logs for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) + continue + } + defer logs.Close() + + outFile, err := os.Create(filepath.Join(namespacedArtifactPath, pod.Name+"-"+container.Name+"-logs.txt")) + if err != nil { + fmt.Printf("Failed to create file for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) + continue + } + defer outFile.Close() + + if _, err := io.Copy(outFile, logs); err != nil { + fmt.Printf("Failed to copy logs for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) + continue + } + } + } + } +} diff --git a/internal/shared/util/testutils/summary.go b/internal/shared/util/testutils/summary.go new file mode 100644 index 0000000000..37c1d51e01 --- /dev/null +++ b/internal/shared/util/testutils/summary.go @@ -0,0 +1,209 @@ +package testutils + +import ( + "context" + "fmt" + "math" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "github.com/prometheus/client_golang/api" + v1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/common/model" +) + +var ( + summaryTemplate = "summary.md.tmpl" + alertsTemplate = "alert.md.tmpl" + chartTemplate = "mermaid_chart.md.tmpl" + defaultPromUrl = "http://localhost:30900/" +) + +type summaryAlerts struct { + FiringAlerts []summaryAlert + PendingAlerts []summaryAlert +} + +type summaryAlert struct { + v1.Alert + Name string + Description string +} + +type xychart struct { + Title string + YMax float64 + YMin float64 + YLabel string + Data string +} + +type githubSummary struct { + client api.Client + Pods []string + alertsFiring bool +} + +func NewSummary(c api.Client, pods ...string) githubSummary { + return githubSummary{ + client: c, + Pods: pods, + alertsFiring: false, + } +} + +// PerformanceQuery queries the prometheus server and generates a mermaid xychart with the data. +// title - Display name of the xychart +// pod - Pod name with which to filter results from prometheus +// query - Prometheus query +// yLabel - Label of the Y axis i.e. "KB/s", "MB", etc. +// scaler - Constant by which to scale the results. For instance, cpu usage is more human-readable +// as "mCPU" vs "CPU", so we scale the results by a factor of 1,000. +func (s *githubSummary) PerformanceQuery(title, pod, query, yLabel string, scaler float64) (string, error) { + v1api := v1.NewAPI(s.client) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + fullQuery := fmt.Sprintf(query, pod) + result, warnings, err := v1api.Query(ctx, fullQuery, time.Now()) + if err != nil { + return "", err + } else if len(warnings) > 0 { + fmt.Printf("warnings returned from performance query; query=%s, warnings=%v", fullQuery, warnings) + } else if result.Type() != model.ValMatrix { + return "", fmt.Errorf("incompatible result type; need: %s, got: %s", model.ValMatrix, result.Type().String()) + } + + matrix, ok := result.(model.Matrix) + if !ok { + return "", fmt.Errorf("typecast for metrics samples failed; aborting") + } else if len(matrix) > 1 { + return "", fmt.Errorf("expected 1 set of results; got: %d", len(matrix)) + } + chart := xychart{ + Title: title, + YLabel: yLabel, + YMax: math.SmallestNonzeroFloat64, + YMin: math.MaxFloat64, + } + formattedData := make([]string, 0) + // matrix does not allow [] access, so we just do one iteration for the single result + for _, metric := range matrix { + if len(metric.Values) < 2 { + // A graph with one data point means something with the collection was wrong + return "", fmt.Errorf("expected at least two data points; got: %d", len(metric.Values)) + } + for _, sample := range metric.Values { + floatSample := float64(sample.Value) * scaler + formattedData = append(formattedData, fmt.Sprintf("%f", floatSample)) + if floatSample > chart.YMax { + chart.YMax = floatSample + } + if floatSample < chart.YMin { + chart.YMin = floatSample + } + } + } + // Add some padding + chart.YMax = (chart.YMax + (math.Abs(chart.YMax) * 0.05)) + chart.YMin = (chart.YMin - (math.Abs(chart.YMin) * 0.05)) + // Pretty print the values, ex: [1,2,3,4] + chart.Data = strings.ReplaceAll(fmt.Sprintf("%v", formattedData), " ", ",") + + return executeTemplate(chartTemplate, chart) +} + +// Alerts queries the prometheus server for alerts and generates markdown output for anything found. +// If no alerts are found, the alerts section will contain only "None." in the final output. +func (s *githubSummary) Alerts() (string, error) { + v1api := v1.NewAPI(s.client) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + result, err := v1api.Alerts(ctx) + if err != nil { + return "", err + } + + firingAlerts := make([]summaryAlert, 0) + pendingAlerts := make([]summaryAlert, 0) + if len(result.Alerts) > 0 { + for _, a := range result.Alerts { + aConv := summaryAlert{ + Alert: a, + Name: string(a.Labels["alertname"]), + Description: string(a.Annotations["description"]), + } + switch a.State { + case v1.AlertStateFiring: + firingAlerts = append(firingAlerts, aConv) + s.alertsFiring = true + case v1.AlertStatePending: + pendingAlerts = append(pendingAlerts, aConv) + // Ignore AlertStateInactive; the alerts endpoint doesn't return them + } + } + } else { + return "None.", nil + } + + return executeTemplate(alertsTemplate, summaryAlerts{ + FiringAlerts: firingAlerts, + PendingAlerts: pendingAlerts, + }) +} + +func executeTemplate(templateFile string, obj any) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("failed to get working directory: %w", err) + } + tmpl, err := template.New(templateFile).ParseGlob(filepath.Join(wd, "../../internal/shared/util/testutils/templates", templateFile)) + if err != nil { + return "", err + } + buffer := new(strings.Builder) + err = tmpl.Execute(buffer, obj) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +// PrintSummary executes the main summary template, generating the full test report. +// The markdown is template-driven; the summary methods are called from within the +// template. This allows us to add or change queries (hopefully) without needing to +// touch code. The summary will be output to a file supplied by the env target. +func PrintSummary(path string) error { + if path == "" { + fmt.Printf("No summary output path specified; skipping") + return nil + } + + client, err := api.NewClient(api.Config{ + Address: defaultPromUrl, + }) + if err != nil { + fmt.Printf("warning: failed to initialize promQL client: %v", err) + return nil + } + + summary := NewSummary(client, "operator-controller", "catalogd") + summaryMarkdown, err := executeTemplate(summaryTemplate, &summary) + if err != nil { + fmt.Printf("warning: failed to generate e2e test summary: %v", err) + return nil + } + err = os.WriteFile(path, []byte(summaryMarkdown), 0o600) + if err != nil { + fmt.Printf("warning: failed to write e2e test summary output to %s: %v", path, err) + return nil + } + fmt.Printf("Test summary output to %s successful\n", path) + if summary.alertsFiring { + return fmt.Errorf("performance alerts encountered during test run; please check e2e test summary for details") + } + return nil +} diff --git a/internal/shared/util/testutils/templates/alert.md.tmpl b/internal/shared/util/testutils/templates/alert.md.tmpl new file mode 100644 index 0000000000..39f3e42879 --- /dev/null +++ b/internal/shared/util/testutils/templates/alert.md.tmpl @@ -0,0 +1,16 @@ +{{- /* -------------------- Alert Template --------------------- */ -}} +{{define "alert"}} +| {{ .Name }} | {{ .Description }} | +| -------- | ------- | +| ActiveAt | {{ .ActiveAt }} | +| State | {{ .State }} | +{{- end}} + +### Firing Alerts +{{ range .FiringAlerts }} +{{ template "alert" .}} +{{ end }} +### Pending Alerts +{{ range .PendingAlerts }} +{{ template "alert" .}} +{{ end }} diff --git a/internal/shared/util/testutils/templates/mermaid_chart.md.tmpl b/internal/shared/util/testutils/templates/mermaid_chart.md.tmpl new file mode 100644 index 0000000000..0a8ed1135b --- /dev/null +++ b/internal/shared/util/testutils/templates/mermaid_chart.md.tmpl @@ -0,0 +1,17 @@ +
+ +```mermaid +--- +config: + xyChart: + showDataLabel: true + xAxis: + showLabel: false +--- +xychart-beta +title "{{ .Title }}" +y-axis "{{ .YLabel }}" {{printf "%f" .YMin}} --> {{printf "%f" .YMax}} +x-axis "time (start of test to end)" +line {{.Data}} +``` +
diff --git a/internal/shared/util/testutils/templates/summary.md.tmpl b/internal/shared/util/testutils/templates/summary.md.tmpl new file mode 100644 index 0000000000..b1372b8740 --- /dev/null +++ b/internal/shared/util/testutils/templates/summary.md.tmpl @@ -0,0 +1,29 @@ + +{{- /* ------------ Performance Statistics Template ------------ */ -}} +{{define "performanceStatistics" -}} +{{ range $index, $pod := .Pods }} +### {{$pod}} +#### Memory Usage +{{$.PerformanceQuery "Memory Usage" $pod `container_memory_working_set_bytes{pod=~"%s.*",container="manager"}[5m]` "MB" .000001}} + +#### Memory Growth Rate +{{$.PerformanceQuery "Memory Growth Rate" $pod `deriv(sum(container_memory_working_set_bytes{pod=~"%s.*",container="manager"})[5m:])[5m:]` "KB/s" .001}} + +#### CPU Usage +{{$.PerformanceQuery "CPU Usage" $pod `rate(container_cpu_usage_seconds_total{pod=~"%s.*",container="manager"}[5m])[5m:]` "mCPU" 1000}} + +#### API Queries Total +{{$.PerformanceQuery "API Queries Total" $pod `sum(rest_client_requests_total{job=~"%s.*"})[5m:]` "# queries" 1}} + +#### API Query Rate +{{$.PerformanceQuery "API Queries/sec" $pod `sum(rate(rest_client_requests_total{job=~"%s.*"}[5m]))[5m:]` "per sec" 1}} + +{{end}} +{{- end}} + +{{- /* ----------------- E2E Summary Markdown ------------------ */ -}} +# E2E Summary +## Alerts +{{.Alerts}} +## Performance +{{ template "performanceStatistics" . -}} diff --git a/internal/shared/util/testutils/utils.go b/internal/shared/util/testutils/utils.go new file mode 100644 index 0000000000..94eb2d5b3c --- /dev/null +++ b/internal/shared/util/testutils/utils.go @@ -0,0 +1,23 @@ +package testutils + +import ( + "os/exec" + "testing" +) + +// FindK8sClient returns the first available Kubernetes CLI client from the system, +// It checks for the existence of each client by running `version --client`. +// If no suitable client is found, the function terminates the test with a failure. +func FindK8sClient(t *testing.T) string { + t.Logf("Finding kubectl client") + clients := []string{"kubectl", "oc"} + for _, c := range clients { + // Would prefer to use `command -v`, but even that may not be installed! + if err := exec.Command(c, "version", "--client").Run(); err == nil { + t.Logf("Using %q as k8s client", c) + return c + } + } + t.Fatal("k8s client not found") + return "" +} diff --git a/internal/shared/util/tlsprofiles/flags.go b/internal/shared/util/tlsprofiles/flags.go new file mode 100644 index 0000000000..682963b497 --- /dev/null +++ b/internal/shared/util/tlsprofiles/flags.go @@ -0,0 +1,189 @@ +package tlsprofiles + +import ( + "bytes" + "crypto/tls" + "encoding/csv" + "fmt" + "maps" + "slices" + "strings" + + "github.com/spf13/pflag" +) + +func AddFlags(fs *pflag.FlagSet) { + fs.Var(&configuredProfile, "tls-profile", "The TLS profile to use. One of "+fmt.Sprintf("%v", slices.Sorted(maps.Keys(profiles)))) + fs.Var(&customTLSProfile.ciphers, "tls-custom-ciphers", "List of ciphers to be used with the custom TLS profile. Use Go-language cipher names") + fs.Var(&customTLSProfile.curves, "tls-custom-curves", "List of curves to be used with the custom TLS profile. Values may consist of "+fmt.Sprintf("%v", slices.Sorted(maps.Keys(curves)))) + fs.Var(&customTLSProfile.minTLSVersion, "tls-custom-version", "The TLS version to be used with the custom TLS profile. One of "+fmt.Sprintf("%v", slices.Sorted(maps.Keys(tlsVersions)))) +} + +// Definition of the type for `--tls-profile` +type tlsProfileName string + +func (p *tlsProfileName) String() string { + return string(*p) +} + +func (p *tlsProfileName) Type() string { + return "string" +} + +func (p *tlsProfileName) Set(value string) error { + newValue := tlsProfileName(value) + _, err := findTLSProfile(newValue) + if err != nil { + return err + } + *p = newValue + return nil +} + +// Definition of the type for `--tls-custom-ciphers` +type cipherSlice struct { + cipherNums []uint16 + cipherNames []string +} + +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + +func writeAsCSV(vals []string) (string, error) { + b := &bytes.Buffer{} + w := csv.NewWriter(b) + err := w.Write(vals) + if err != nil { + return "", err + } + w.Flush() + return strings.TrimSuffix(b.String(), "\n"), nil +} + +func (s *cipherSlice) Set(val string) error { + v, err := readAsCSV(val) + if err != nil { + return err + } + return s.Replace(v) +} + +func (s *cipherSlice) Type() string { + return "stringSlice" +} + +func (s *cipherSlice) String() string { + str, _ := writeAsCSV(s.cipherNames) + return "[" + str + "]" +} + +func (s *cipherSlice) Append(val string) error { + num := cipherSuiteId(val) + if num == 0 { + return fmt.Errorf("unknown cipher %q", val) + } + s.cipherNums = append(s.cipherNums, num) + s.cipherNames = append(s.cipherNames, val) + return nil +} + +func (s *cipherSlice) Replace(val []string) error { + s.cipherNames = make([]string, 0, len(val)) + s.cipherNums = make([]uint16, 0, len(val)) + for _, cipher := range val { + if err := s.Append(cipher); err != nil { + return err + } + } + return nil +} + +func (s *cipherSlice) GetSlice() []string { + return s.cipherNames +} + +// Definition of the type for `--tls-custom-curves` +type curveSlice struct { + curveNums []tls.CurveID + curveNames []string +} + +func (s *curveSlice) Set(val string) error { + v, err := readAsCSV(val) + if err != nil { + return err + } + return s.Replace(v) +} + +func (s *curveSlice) Type() string { + return "stringSlice" +} + +func (s *curveSlice) String() string { + str, _ := writeAsCSV(s.curveNames) + return "[" + str + "]" +} + +func (s *curveSlice) Append(val string) error { + num := curveId(val) + if num == 0 { + return fmt.Errorf("unknown curve %q", val) + } + s.curveNums = append(s.curveNums, num) + s.curveNames = append(s.curveNames, val) + return nil +} + +func (s *curveSlice) Replace(val []string) error { + s.curveNames = make([]string, 0, len(val)) + s.curveNums = make([]tls.CurveID, 0, len(val)) + for _, curve := range val { + if err := s.Append(curve); err != nil { + return err + } + } + return nil +} + +func (s *curveSlice) GetSlice() []string { + return s.curveNames +} + +// Definition of the type for `--tls-custom-version` +type tlsVersion uint16 + +var tlsVersions = map[string]uint16{ + "TLSv1.0": tls.VersionTLS10, + "TLSv1.1": tls.VersionTLS11, + "TLSv1.2": tls.VersionTLS12, + "TLSv1.3": tls.VersionTLS13, +} + +func (v *tlsVersion) String() string { + for s, n := range tlsVersions { + if *v == tlsVersion(n) { + return s + } + } + return "" +} + +func (v *tlsVersion) Type() string { + return "string" +} + +func (v *tlsVersion) Set(value string) error { + n, ok := tlsVersions[value] + if !ok { + return fmt.Errorf("unknown TLS version: %q", value) + } + *v = tlsVersion(n) + return nil +} diff --git a/internal/shared/util/tlsprofiles/mozilla_data.go b/internal/shared/util/tlsprofiles/mozilla_data.go new file mode 100644 index 0000000000..6b74ade2d8 --- /dev/null +++ b/internal/shared/util/tlsprofiles/mozilla_data.go @@ -0,0 +1,87 @@ +package tlsprofiles + +// DO NOT EDIT, GENERATED BY hack/tools/update-tls-profiles.sh +// DATA SOURCE: https://ssl-config.mozilla.org/guidelines/latest.json +// DATA VERSION: 5.7 + +import ( + "crypto/tls" +) + +var modernTLSProfile = tlsProfile{ + ciphers: cipherSlice{ + cipherNums: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + }, + }, + curves: curveSlice{ + curveNums: []tls.CurveID{ + X25519, + prime256v1, + secp384r1, + }, + }, + minTLSVersion: tls.VersionTLS13, +} + +var intermediateTLSProfile = tlsProfile{ + ciphers: cipherSlice{ + cipherNums: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + }, + }, + curves: curveSlice{ + curveNums: []tls.CurveID{ + X25519, + prime256v1, + secp384r1, + }, + }, + minTLSVersion: tls.VersionTLS12, +} + +var oldTLSProfile = tlsProfile{ + ciphers: cipherSlice{ + cipherNums: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }, + }, + curves: curveSlice{ + curveNums: []tls.CurveID{ + X25519, + prime256v1, + secp384r1, + }, + }, + minTLSVersion: tls.VersionTLS10, +} diff --git a/internal/shared/util/tlsprofiles/tlsprofiles.go b/internal/shared/util/tlsprofiles/tlsprofiles.go new file mode 100644 index 0000000000..bac6586101 --- /dev/null +++ b/internal/shared/util/tlsprofiles/tlsprofiles.go @@ -0,0 +1,91 @@ +package tlsprofiles + +import ( + "crypto/tls" + "fmt" + "strings" +) + +var ( + configuredProfile tlsProfileName = "intermediate" + customTLSProfile tlsProfile = tlsProfile{ + ciphers: cipherSlice{}, + curves: curveSlice{}, + minTLSVersion: tls.VersionTLS12, + } +) + +type tlsProfile struct { + ciphers cipherSlice + curves curveSlice + minTLSVersion tlsVersion +} + +// Based on compatibility levels from: https://wiki.mozilla.org/Security/Server_Side_TLS +var profiles = map[string]*tlsProfile{ + "modern": &modernTLSProfile, + "intermediate": &intermediateTLSProfile, + "old": &oldTLSProfile, + "custom": &customTLSProfile, +} + +func findTLSProfile(profile tlsProfileName) (*tlsProfile, error) { + p := strings.ToLower(profile.String()) + tlsProfile, ok := profiles[p] + if !ok { + return nil, fmt.Errorf("unknown TLS profile: %q", profile.String()) + } + return tlsProfile, nil +} + +func GetTLSConfigFunc() (func(*tls.Config), error) { + tlsProfile, err := findTLSProfile(configuredProfile) + if err != nil { + return nil, err + } + return func(config *tls.Config) { + config.MinVersion = uint16(tlsProfile.minTLSVersion) + config.CipherSuites = tlsProfile.ciphers.cipherNums + config.CurvePreferences = tlsProfile.curves.curveNums + }, nil +} + +// This function should _really_ exist in crypto/tls +// cipherSuiteId returns the cipher suite ID given a standard name +// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or 0 if no such cipher exists +func cipherSuiteId(name string) uint16 { + for _, c := range tls.CipherSuites() { + if c.Name == name { + return c.ID + } + } + for _, c := range tls.InsecureCipherSuites() { + if c.Name == name { + return c.ID + } + } + return 0 +} + +// This is primarily so that we don't have to rewrite curve values in mozilla_data.go +const ( + X25519 tls.CurveID = tls.X25519 + prime256v1 tls.CurveID = tls.CurveP256 + secp384r1 tls.CurveID = tls.CurveP384 + secp521r1 tls.CurveID = tls.CurveP521 +) + +var curves = map[string]tls.CurveID{ + "X25519": tls.X25519, + "prime256v1": tls.CurveP256, + "secp384r1": tls.CurveP384, + "secp521r1": tls.CurveP521, +} + +// Returns 0 for an invalid curve name +func curveId(name string) tls.CurveID { + if id, ok := curves[name]; ok { + return id + } + return 0 +} diff --git a/internal/shared/util/tlsprofiles/tlsprofiles_test.go b/internal/shared/util/tlsprofiles/tlsprofiles_test.go new file mode 100644 index 0000000000..7be0903f79 --- /dev/null +++ b/internal/shared/util/tlsprofiles/tlsprofiles_test.go @@ -0,0 +1,160 @@ +package tlsprofiles + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetProfiles(t *testing.T) { + tests := []struct { + name tlsProfileName + result bool + }{ + {"modern", true}, + {"intermediate", true}, + {"old", true}, + {"custom", true}, + {"does-not-exist", false}, + } + + for _, test := range tests { + p, err := findTLSProfile(test.name) + if !test.result { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, p) + } + } +} + +func TestGetTLSConfigFunc(t *testing.T) { + f, err := GetTLSConfigFunc() + require.NoError(t, err) + require.NotNil(t, f) + + // Set an invalid profile + configuredProfile = "does-not-exist" + f, err = GetTLSConfigFunc() + require.Error(t, err) + require.Nil(t, f) +} + +func TestCipherStuiteId(t *testing.T) { + tests := []struct { + name string + result uint16 + }{ + {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xC009}, + {"unknown-cipher", 0}, + {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", 0x000A}, // Insecure cipher + {"DHE-RSA-AES128-SHA256", 0}, // Valid OpenSSL cipher, not implemented + } + + for _, test := range tests { + v := cipherSuiteId(test.name) + require.Equal(t, test.result, v) + } +} + +func TestSetProfileName(t *testing.T) { + var profile tlsProfileName + + tests := []struct { + name string + result bool + }{ + {"modern", true}, + {"intermediate", true}, + {"old", true}, + {"custom", true}, + {"does-not-exist", false}, + } + + for _, test := range tests { + err := (&profile).Set(test.name) + if !test.result { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } +} + +func TestSetCustomCipher(t *testing.T) { + var ciphers cipherSlice + + tests := []struct { + name string + result bool + }{ + {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", true}, + {"unknown-cipher", false}, + {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", true}, // Insecure cipher + {"DHE-RSA-AES128-SHA256", false}, // Valid OpenSSL cipher, not implemented + {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA", true}, // Multiple + } + + for _, test := range tests { + err := ciphers.Set(test.name) + if test.result { + require.NoError(t, err) + require.Equal(t, "["+test.name+"]", ciphers.String()) + } else { + require.Error(t, err) + } + } +} + +func TestSetCustomCurves(t *testing.T) { + var curves curveSlice + + tests := []struct { + name string + result bool + }{ + {"X25519", true}, + {"prime256v1", true}, + {"secp384r1", true}, + {"secp521r1", true}, + {"unknown-cuve", false}, + {"X448", false}, // Valid OpenSSL curve, not implemented + {"X25519,prime256v1", true}, // Multiple + } + + for _, test := range tests { + err := curves.Set(test.name) + if test.result { + require.NoError(t, err) + require.Equal(t, "["+test.name+"]", curves.String()) + } else { + require.Error(t, err) + } + } +} + +func TestSetCustomVersion(t *testing.T) { + var version tlsVersion + + tests := []struct { + name string + result bool + }{ + {"TLSv1.0", true}, + {"TLSv1.1", true}, + {"TLSv1.2", true}, + {"TLSv1.3", true}, + {"unknown-version", false}, + } + + for _, test := range tests { + err := version.Set(test.name) + if test.result { + require.NoError(t, err) + require.Equal(t, test.name, version.String()) + } else { + require.Error(t, err) + } + } +} diff --git a/internal/version/version.go b/internal/shared/version/version.go similarity index 91% rename from internal/version/version.go rename to internal/shared/version/version.go index ef90dffa01..e61952e913 100644 --- a/internal/version/version.go +++ b/internal/shared/version/version.go @@ -29,7 +29,9 @@ func init() { for _, setting := range info.Settings { switch setting.Key { case "vcs.revision": - gitCommit = setting.Value + if gitCommit == "unknown" { + gitCommit = setting.Value + } case "vcs.time": commitDate = setting.Value case "vcs.modified": diff --git a/kind-config.yaml b/kind-config.yaml index 6cdeef6220..5b5b3b9139 100644 --- a/kind-config.yaml +++ b/kind-config.yaml @@ -8,9 +8,21 @@ nodes: hostPort: 30000 listenAddress: "127.0.0.1" protocol: tcp + # prometheus metrics service's NodePort + - containerPort: 30900 + hostPort: 30900 + listenAddress: "127.0.0.1" + protocol: tcp kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: enable-admission-plugins: OwnerReferencesPermissionEnforcement + extraMounts: + - hostPath: ./hack/kind-config/containerd/certs.d + containerPath: /etc/containerd/certs.d +containerdConfigPatches: + - |- + [plugins."io.containerd.grpc.v1.cri".registry] + config_path = "/etc/containerd/certs.d" diff --git a/manifests/OWNERS b/manifests/OWNERS new file mode 100644 index 0000000000..b44dad0ea8 --- /dev/null +++ b/manifests/OWNERS @@ -0,0 +1,2 @@ +approvers: + - manifest-approvers diff --git a/manifests/default-catalogs.yaml b/manifests/default-catalogs.yaml new file mode 100644 index 0000000000..a656b35095 --- /dev/null +++ b/manifests/default-catalogs.yaml @@ -0,0 +1,11 @@ +apiVersion: olm.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: operatorhubio + namespace: olmv1-system +spec: + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + pollIntervalMinutes: 10 diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml new file mode 100644 index 0000000000..1efa8b8d99 --- /dev/null +++ b/manifests/experimental-e2e.yaml @@ -0,0 +1,2454 @@ +--- +# Source: olmv1/templates/namespace.yml +apiVersion: v1 +kind: Namespace +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/audit-version: latest + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/enforce-version: latest + pod-security.kubernetes.io/warn: restricted + pod-security.kubernetes.io/warn-version: latest + app.kubernetes.io/part-of: olm + name: olmv1-system +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 7443 + protocol: TCP + - port: 8443 + protocol: TCP + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + control-plane: catalogd-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: default-deny-all-traffic + namespace: olmv1-system +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 8443 + protocol: TCP + podSelector: + matchLabels: + control-plane: operator-controller-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/e2e/configmap-olmv1-system-e2e-registries-conf.yml +apiVersion: v1 +data: + registries.conf: | + [[registry]] + prefix = "mirrored-registry.operator-controller-e2e.svc.cluster.local:5000" + location = "docker-registry.operator-controller-e2e.svc.cluster.local:5000" +kind: ConfigMap +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-registries-conf + namespace: olmv1-system +--- +# Source: olmv1/templates/e2e/persistentvolumeclaim-olmv1-system-e2e-coverage.yml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-coverage + namespace: olmv1-system +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 64Mi +--- +# Source: olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clustercatalogs.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterCatalog + listKind: ClusterCatalogList + plural: clustercatalogs + singular: clustercatalog + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastUnpacked + name: LastUnpacked + type: date + - jsonPath: .status.conditions[?(@.type=="Serving")].status + name: Serving + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is the desired state of the ClusterCatalog. + spec is required. + The controller will work to ensure that the desired + catalog is unpacked and served over the catalog content HTTP server. + properties: + availabilityMode: + default: Available + description: |- + availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + availabilityMode is optional. + + Allowed values are "Available" and "Unavailable" and omitted. + + When omitted, the default value is "Available". + + When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + and its contents as usable. + + When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + enum: + - Unavailable + - Available + type: string + priority: + default: 0 + description: |- + priority allows the user to define a priority for a ClusterCatalog. + priority is optional. + + A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + A higher number means higher priority. + + It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + + When omitted, the default priority is 0 because that is the zero value of integers. + + Negative numbers can be used to specify a priority lower than the default. + Positive numbers can be used to specify a priority higher than the default. + + The lowest possible value is -2147483648. + The highest possible value is 2147483647. + format: int32 + type: integer + source: + description: |- + source allows a user to define the source of a catalog. + A "catalog" contains information on content that can be installed on a cluster. + Providing a catalog source makes the contents of the catalog discoverable and usable by + other on-cluster components. + These on-cluster components may do a variety of things with this information, such as + presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + source is a required field. + + Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + properties: + image: + description: |- + image is used to configure how catalog contents are sourced from an OCI image. + This field is required when type is Image, and forbidden otherwise. + properties: + pollIntervalMinutes: + description: |- + pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + pollIntervalMinutes is optional. + pollIntervalMinutes can not be specified when ref is a digest-based reference. + + When omitted, the image will not be polled for new content. + minimum: 1 + type: integer + ref: + description: |- + ref allows users to define the reference to a container image containing Catalog contents. + ref is required. + ref can not be more than 1000 characters. + + A reference can be broken down into 3 parts - the domain, name, and identifier. + + The domain is typically the registry where an image is located. + It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + The port must be the last value in the domain. + Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + + The name is typically the repository in the registry where an image is located. + It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + Multiple names can be concatenated with the "/" character. + The domain and name are combined using the "/" character. + Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + + The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + For a digest-based reference, the "@" character is the separator. + For a tag-based reference, the ":" character is the separator. + An identifier is required in the reference. + + Digest-based references must contain an algorithm reference immediately after the "@" separator. + The algorithm reference must be followed by the ":" character and an encoded string. + The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + + Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + The tag must not be longer than 127 characters. + + An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != "" || self.find(':.*$') != + "" + - message: tag is invalid. the tag must not be more than 127 + characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').substring(1).size() <= 127 + : true) : true' + - message: tag is invalid. valid tags must begin with a word + character (alphanumeric + "_") followed by word characters + or ".", and "-" characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensionrevisions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensionrevisions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtensionRevision + listKind: ClusterExtensionRevisionList + plural: clusterextensionrevisions + singular: clusterextensionrevision + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtensionRevision is the Schema for the clusterextensionrevisions + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + lifecycleState: + default: Active + description: Specifies the lifecycle state of the ClusterExtensionRevision. + enum: + - Active + - Paused + - Archived + type: string + x-kubernetes-validations: + - message: can not un-archive + rule: oldSelf == 'Active' || oldSelf == 'Paused' || oldSelf == 'Archived' + && oldSelf == self + phases: + description: |- + Phases are groups of objects that will be applied at the same time. + All objects in the phase will have to pass their probes in order to progress to the next phase. + items: + description: |- + ClusterExtensionRevisionPhase are groups of objects that will be applied at the same time. + All objects in the a phase will have to pass their probes in order to progress to the next phase. + properties: + name: + description: Name identifies this phase. + maxLength: 63 + pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ + type: string + objects: + description: Objects are a list of all the objects within this + phase. + items: + description: ClusterExtensionRevisionObject contains an object + and settings for it. + properties: + collisionProtection: + default: Prevent + description: |- + CollisionProtection controls whether OLM can adopt and modify objects + already existing on the cluster or even owned by another controller. + type: string + object: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + required: + - object + type: object + type: array + required: + - name + - objects + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: phases is immutable + rule: self == oldSelf || oldSelf.size() == 0 + previous: + description: Previous references previous revisions that objects can + be adopted from. + items: + properties: + name: + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + required: + - name + - uid + type: object + type: array + x-kubernetes-validations: + - message: previous is immutable + rule: self == oldSelf + revision: + description: |- + Revision is a sequence number representing a specific revision of the ClusterExtension instance. + Must be positive. Each ClusterExtensionRevision of the same parent ClusterExtension needs to have + a unique value assigned. It is immutable after creation. The new revision number must always be previous revision +1. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: revision is immutable + rule: self == oldSelf + required: + - revision + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + config: + description: |- + config is an optional field used to specify bundle specific configuration + used to configure the bundle. Configuration is bundle specific and a bundle may provide + a configuration schema. When not specified, the default configuration of the resolved bundle will be used. + + config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide + a configuration schema the final manifests will be derived on a best-effort basis. More information on how + to configure the bundle should be found in its end-user documentation. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + inline accepts arbitrary JSON/YAML objects. + inline is validation at runtime against the schema provided by the bundle if a schema is provided. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: catalogd-manager-role + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental-e2e +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/status + verbs: + - get + - patch + - update +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-clusterextension-viewer-role +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-controller-manager-role + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental-e2e +rules: + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - get + - list + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/status + verbs: + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-proxy-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-proxy-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-admin-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: catalogd-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental-e2e +rules: + - apiGroups: + - "" + resources: + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-controller-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental-e2e +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-leader-election-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-leader-election-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/service-olmv1-system-catalogd-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service + namespace: olmv1-system +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + - name: webhook + port: 9443 + protocol: TCP + targetPort: 9443 + - name: metrics + port: 7443 + protocol: TCP + targetPort: 7443 + selector: + app.kubernetes.io/name: catalogd +--- +# Source: olmv1/templates/service-olmv1-system-operator-controller-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-service + namespace: olmv1-system +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: operator-controller +--- +# Source: olmv1/templates/e2e/pod-olmv1-system-e2e-coverage-copy-pod.yml +apiVersion: v1 +kind: Pod +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-coverage-copy-pod + namespace: olmv1-system +spec: + containers: + - command: + - sleep + - infinity + image: busybox:1.36 + name: tar + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /e2e-coverage + name: e2e-coverage-volume + readOnly: true + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + volumes: + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + readOnly: true +--- +# Source: olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + minReadySeconds: 5 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: catalogd-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + control-plane: catalogd-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --leader-elect + - --metrics-bind-address=:7443 + - --external-address=catalogd-service.olmv1-system.svc + - --feature-gates=APIV1MetasHandler=true + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --pull-cas-dir=/var/ca-certs + - --tls-profile=custom + - --tls-custom-version=TLSv1.3 + - --tls-custom-curves=X25519,prime256v1 + - --tls-custom-ciphers=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384 + command: + - ./catalogd + env: + - name: GOCOVERDIR + value: /e2e-coverage + image: "quay.io/operator-framework/catalogd:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /e2e-coverage + name: e2e-coverage-volume + - mountPath: /var/cache/ + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: catalogd-controller-manager + volumes: + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogd-service-cert-git-version + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: catalogd-service-cert-git-version + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: operator-controller-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: operator-controller + control-plane: operator-controller-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + - --leader-elect + - --feature-gates=SingleOwnNamespaceInstallSupport=true + - --feature-gates=PreflightPermissions=true + - --feature-gates=HelmChartSupport=true + - --feature-gates=BoxcutterRuntime=true + - --feature-gates=WebhookProviderOpenshiftServiceCA=false + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --pull-cas-dir=/var/ca-certs + - --tls-profile=modern + command: + - /operator-controller + env: + - name: GOCOVERDIR + value: /e2e-coverage + image: "quay.io/operator-framework/operator-controller:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + - mountPath: /etc/containers + name: e2e-registries-conf + - mountPath: /e2e-coverage + name: e2e-coverage-volume + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: operator-controller-certs + readOnly: true + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: operator-controller-controller-manager + volumes: + - configMap: + name: e2e-registries-conf + name: e2e-registries-conf + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: operator-controller-cert + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca + namespace: cert-manager +spec: + commonName: olmv1-ca + isCA: true + issuerRef: + group: cert-manager.io + kind: Issuer + name: self-sign-issuer + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: olmv1-ca + secretTemplate: + annotations: + cert-manager.io/allow-direct-injection: "true" +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service-cert + namespace: olmv1-system +spec: + dnsNames: + - localhost + - catalogd-service.olmv1-system.svc + - catalogd-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: catalogd-service-cert-git-version +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: operator-controller-cert + namespace: olmv1-system +spec: + dnsNames: + - operator-controller-service.olmv1-system.svc + - operator-controller-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: operator-controller-cert +--- +# Source: olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca +spec: + ca: + secretName: olmv1-ca +--- +# Source: olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: self-sign-issuer + namespace: cert-manager +spec: + selfSigned: {} +--- +# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: catalogd-mutating-webhook-configuration + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + cert-manager.io/inject-ca-from-secret: cert-manager/olmv1-ca + olm.operatorframework.io/feature-set: experimental-e2e +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: catalogd-service + namespace: olmv1-system + path: /mutate-olm-operatorframework-io-v1-clustercatalog + port: 9443 + failurePolicy: Fail + name: inject-metadata-name.olm.operatorframework.io + rules: + - apiGroups: + - olm.operatorframework.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - clustercatalogs + sideEffects: None + timeoutSeconds: 10 + matchConditions: + - name: MissingOrIncorrectMetadataNameLabel + expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml new file mode 100644 index 0000000000..664f8599cc --- /dev/null +++ b/manifests/experimental.yaml @@ -0,0 +1,2353 @@ +--- +# Source: olmv1/templates/namespace.yml +apiVersion: v1 +kind: Namespace +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/audit-version: latest + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/enforce-version: latest + pod-security.kubernetes.io/warn: restricted + pod-security.kubernetes.io/warn-version: latest + app.kubernetes.io/part-of: olm + name: olmv1-system +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 7443 + protocol: TCP + - port: 8443 + protocol: TCP + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + control-plane: catalogd-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: default-deny-all-traffic + namespace: olmv1-system +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 8443 + protocol: TCP + podSelector: + matchLabels: + control-plane: operator-controller-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clustercatalogs.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterCatalog + listKind: ClusterCatalogList + plural: clustercatalogs + singular: clustercatalog + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastUnpacked + name: LastUnpacked + type: date + - jsonPath: .status.conditions[?(@.type=="Serving")].status + name: Serving + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is the desired state of the ClusterCatalog. + spec is required. + The controller will work to ensure that the desired + catalog is unpacked and served over the catalog content HTTP server. + properties: + availabilityMode: + default: Available + description: |- + availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + availabilityMode is optional. + + Allowed values are "Available" and "Unavailable" and omitted. + + When omitted, the default value is "Available". + + When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + and its contents as usable. + + When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + enum: + - Unavailable + - Available + type: string + priority: + default: 0 + description: |- + priority allows the user to define a priority for a ClusterCatalog. + priority is optional. + + A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + A higher number means higher priority. + + It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + + When omitted, the default priority is 0 because that is the zero value of integers. + + Negative numbers can be used to specify a priority lower than the default. + Positive numbers can be used to specify a priority higher than the default. + + The lowest possible value is -2147483648. + The highest possible value is 2147483647. + format: int32 + type: integer + source: + description: |- + source allows a user to define the source of a catalog. + A "catalog" contains information on content that can be installed on a cluster. + Providing a catalog source makes the contents of the catalog discoverable and usable by + other on-cluster components. + These on-cluster components may do a variety of things with this information, such as + presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + source is a required field. + + Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + properties: + image: + description: |- + image is used to configure how catalog contents are sourced from an OCI image. + This field is required when type is Image, and forbidden otherwise. + properties: + pollIntervalMinutes: + description: |- + pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + pollIntervalMinutes is optional. + pollIntervalMinutes can not be specified when ref is a digest-based reference. + + When omitted, the image will not be polled for new content. + minimum: 1 + type: integer + ref: + description: |- + ref allows users to define the reference to a container image containing Catalog contents. + ref is required. + ref can not be more than 1000 characters. + + A reference can be broken down into 3 parts - the domain, name, and identifier. + + The domain is typically the registry where an image is located. + It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + The port must be the last value in the domain. + Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + + The name is typically the repository in the registry where an image is located. + It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + Multiple names can be concatenated with the "/" character. + The domain and name are combined using the "/" character. + Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + + The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + For a digest-based reference, the "@" character is the separator. + For a tag-based reference, the ":" character is the separator. + An identifier is required in the reference. + + Digest-based references must contain an algorithm reference immediately after the "@" separator. + The algorithm reference must be followed by the ":" character and an encoded string. + The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + + Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + The tag must not be longer than 127 characters. + + An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != "" || self.find(':.*$') != + "" + - message: tag is invalid. the tag must not be more than 127 + characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').substring(1).size() <= 127 + : true) : true' + - message: tag is invalid. valid tags must begin with a word + character (alphanumeric + "_") followed by word characters + or ".", and "-" characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensionrevisions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensionrevisions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtensionRevision + listKind: ClusterExtensionRevisionList + plural: clusterextensionrevisions + singular: clusterextensionrevision + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtensionRevision is the Schema for the clusterextensionrevisions + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + lifecycleState: + default: Active + description: Specifies the lifecycle state of the ClusterExtensionRevision. + enum: + - Active + - Paused + - Archived + type: string + x-kubernetes-validations: + - message: can not un-archive + rule: oldSelf == 'Active' || oldSelf == 'Paused' || oldSelf == 'Archived' + && oldSelf == self + phases: + description: |- + Phases are groups of objects that will be applied at the same time. + All objects in the phase will have to pass their probes in order to progress to the next phase. + items: + description: |- + ClusterExtensionRevisionPhase are groups of objects that will be applied at the same time. + All objects in the a phase will have to pass their probes in order to progress to the next phase. + properties: + name: + description: Name identifies this phase. + maxLength: 63 + pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ + type: string + objects: + description: Objects are a list of all the objects within this + phase. + items: + description: ClusterExtensionRevisionObject contains an object + and settings for it. + properties: + collisionProtection: + default: Prevent + description: |- + CollisionProtection controls whether OLM can adopt and modify objects + already existing on the cluster or even owned by another controller. + type: string + object: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + required: + - object + type: object + type: array + required: + - name + - objects + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: phases is immutable + rule: self == oldSelf || oldSelf.size() == 0 + previous: + description: Previous references previous revisions that objects can + be adopted from. + items: + properties: + name: + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + required: + - name + - uid + type: object + type: array + x-kubernetes-validations: + - message: previous is immutable + rule: self == oldSelf + revision: + description: |- + Revision is a sequence number representing a specific revision of the ClusterExtension instance. + Must be positive. Each ClusterExtensionRevision of the same parent ClusterExtension needs to have + a unique value assigned. It is immutable after creation. The new revision number must always be previous revision +1. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: revision is immutable + rule: self == oldSelf + required: + - revision + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: experimental + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + config: + description: |- + config is an optional field used to specify bundle specific configuration + used to configure the bundle. Configuration is bundle specific and a bundle may provide + a configuration schema. When not specified, the default configuration of the resolved bundle will be used. + + config is validated against a configuration schema provided by the resolved bundle. If the bundle does not provide + a configuration schema the final manifests will be derived on a best-effort basis. More information on how + to configure the bundle should be found in its end-user documentation. + properties: + configType: + description: |- + configType is a required reference to the type of configuration source. + + Allowed values are "Inline" + + When this field is set to "Inline", the cluster extension configuration is defined inline within the + ClusterExtension resource. + enum: + - Inline + type: string + inline: + description: |- + inline contains JSON or YAML values specified directly in the + ClusterExtension. + + inline must be set if configType is 'Inline'. + inline accepts arbitrary JSON/YAML objects. + inline is validation at runtime against the schema provided by the bundle if a schema is provided. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - configType + type: object + x-kubernetes-validations: + - message: inline is required when configType is Inline, and forbidden + otherwise + rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline) + : !has(self.inline)' + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: catalogd-manager-role + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/status + verbs: + - get + - patch + - update +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-clusterextension-viewer-role +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-controller-manager-role + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental +rules: + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - get + - list + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/status + verbs: + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-proxy-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-proxy-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-admin-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: catalogd-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental +rules: + - apiGroups: + - "" + resources: + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-controller-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: experimental +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-leader-election-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-leader-election-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/service-olmv1-system-catalogd-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service + namespace: olmv1-system +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + - name: webhook + port: 9443 + protocol: TCP + targetPort: 9443 + - name: metrics + port: 7443 + protocol: TCP + targetPort: 7443 + selector: + app.kubernetes.io/name: catalogd +--- +# Source: olmv1/templates/service-olmv1-system-operator-controller-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-service + namespace: olmv1-system +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: operator-controller +--- +# Source: olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + minReadySeconds: 5 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: catalogd-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + control-plane: catalogd-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --leader-elect + - --metrics-bind-address=:7443 + - --external-address=catalogd-service.olmv1-system.svc + - --feature-gates=APIV1MetasHandler=true + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --pull-cas-dir=/var/ca-certs + command: + - ./catalogd + image: "quay.io/operator-framework/catalogd:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /var/cache/ + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: catalogd-controller-manager + volumes: + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogd-service-cert-git-version + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: catalogd-service-cert-git-version + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: operator-controller-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: operator-controller + control-plane: operator-controller-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + - --leader-elect + - --feature-gates=SingleOwnNamespaceInstallSupport=true + - --feature-gates=PreflightPermissions=true + - --feature-gates=HelmChartSupport=true + - --feature-gates=BoxcutterRuntime=true + - --feature-gates=WebhookProviderOpenshiftServiceCA=false + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --pull-cas-dir=/var/ca-certs + command: + - /operator-controller + image: "quay.io/operator-framework/operator-controller:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: operator-controller-certs + readOnly: true + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: operator-controller-controller-manager + volumes: + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: operator-controller-cert + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca + namespace: cert-manager +spec: + commonName: olmv1-ca + isCA: true + issuerRef: + group: cert-manager.io + kind: Issuer + name: self-sign-issuer + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: olmv1-ca + secretTemplate: + annotations: + cert-manager.io/allow-direct-injection: "true" +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service-cert + namespace: olmv1-system +spec: + dnsNames: + - localhost + - catalogd-service.olmv1-system.svc + - catalogd-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: catalogd-service-cert-git-version +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: operator-controller-cert + namespace: olmv1-system +spec: + dnsNames: + - operator-controller-service.olmv1-system.svc + - operator-controller-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: operator-controller-cert +--- +# Source: olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca +spec: + ca: + secretName: olmv1-ca +--- +# Source: olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + annotations: + olm.operatorframework.io/feature-set: experimental + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: self-sign-issuer + namespace: cert-manager +spec: + selfSigned: {} +--- +# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: catalogd-mutating-webhook-configuration + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + cert-manager.io/inject-ca-from-secret: cert-manager/olmv1-ca + olm.operatorframework.io/feature-set: experimental +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: catalogd-service + namespace: olmv1-system + path: /mutate-olm-operatorframework-io-v1-clustercatalog + port: 9443 + failurePolicy: Fail + name: inject-metadata-name.olm.operatorframework.io + rules: + - apiGroups: + - olm.operatorframework.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - clustercatalogs + sideEffects: None + timeoutSeconds: 10 + matchConditions: + - name: MissingOrIncorrectMetadataNameLabel + expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" diff --git a/manifests/standard-e2e.yaml b/manifests/standard-e2e.yaml new file mode 100644 index 0000000000..783beec515 --- /dev/null +++ b/manifests/standard-e2e.yaml @@ -0,0 +1,2195 @@ +--- +# Source: olmv1/templates/namespace.yml +apiVersion: v1 +kind: Namespace +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/audit-version: latest + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/enforce-version: latest + pod-security.kubernetes.io/warn: restricted + pod-security.kubernetes.io/warn-version: latest + app.kubernetes.io/part-of: olm + name: olmv1-system +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 7443 + protocol: TCP + - port: 8443 + protocol: TCP + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + control-plane: catalogd-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: default-deny-all-traffic + namespace: olmv1-system +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 8443 + protocol: TCP + podSelector: + matchLabels: + control-plane: operator-controller-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/e2e/configmap-olmv1-system-e2e-registries-conf.yml +apiVersion: v1 +data: + registries.conf: | + [[registry]] + prefix = "mirrored-registry.operator-controller-e2e.svc.cluster.local:5000" + location = "docker-registry.operator-controller-e2e.svc.cluster.local:5000" +kind: ConfigMap +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-registries-conf + namespace: olmv1-system +--- +# Source: olmv1/templates/e2e/persistentvolumeclaim-olmv1-system-e2e-coverage.yml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-coverage + namespace: olmv1-system +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 64Mi +--- +# Source: olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clustercatalogs.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterCatalog + listKind: ClusterCatalogList + plural: clustercatalogs + singular: clustercatalog + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastUnpacked + name: LastUnpacked + type: date + - jsonPath: .status.conditions[?(@.type=="Serving")].status + name: Serving + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is the desired state of the ClusterCatalog. + spec is required. + The controller will work to ensure that the desired + catalog is unpacked and served over the catalog content HTTP server. + properties: + availabilityMode: + default: Available + description: |- + availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + availabilityMode is optional. + + Allowed values are "Available" and "Unavailable" and omitted. + + When omitted, the default value is "Available". + + When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + and its contents as usable. + + When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + enum: + - Unavailable + - Available + type: string + priority: + default: 0 + description: |- + priority allows the user to define a priority for a ClusterCatalog. + priority is optional. + + A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + A higher number means higher priority. + + It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + + When omitted, the default priority is 0 because that is the zero value of integers. + + Negative numbers can be used to specify a priority lower than the default. + Positive numbers can be used to specify a priority higher than the default. + + The lowest possible value is -2147483648. + The highest possible value is 2147483647. + format: int32 + type: integer + source: + description: |- + source allows a user to define the source of a catalog. + A "catalog" contains information on content that can be installed on a cluster. + Providing a catalog source makes the contents of the catalog discoverable and usable by + other on-cluster components. + These on-cluster components may do a variety of things with this information, such as + presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + source is a required field. + + Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + properties: + image: + description: |- + image is used to configure how catalog contents are sourced from an OCI image. + This field is required when type is Image, and forbidden otherwise. + properties: + pollIntervalMinutes: + description: |- + pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + pollIntervalMinutes is optional. + pollIntervalMinutes can not be specified when ref is a digest-based reference. + + When omitted, the image will not be polled for new content. + minimum: 1 + type: integer + ref: + description: |- + ref allows users to define the reference to a container image containing Catalog contents. + ref is required. + ref can not be more than 1000 characters. + + A reference can be broken down into 3 parts - the domain, name, and identifier. + + The domain is typically the registry where an image is located. + It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + The port must be the last value in the domain. + Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + + The name is typically the repository in the registry where an image is located. + It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + Multiple names can be concatenated with the "/" character. + The domain and name are combined using the "/" character. + Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + + The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + For a digest-based reference, the "@" character is the separator. + For a tag-based reference, the ":" character is the separator. + An identifier is required in the reference. + + Digest-based references must contain an algorithm reference immediately after the "@" separator. + The algorithm reference must be followed by the ":" character and an encoded string. + The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + + Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + The tag must not be longer than 127 characters. + + An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != "" || self.find(':.*$') != + "" + - message: tag is invalid. the tag must not be more than 127 + characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').substring(1).size() <= 127 + : true) : true' + - message: tag is invalid. valid tags must begin with a word + character (alphanumeric + "_") followed by word characters + or ".", and "-" characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: catalogd-manager-role + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard-e2e +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/status + verbs: + - get + - patch + - update +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-clusterextension-viewer-role +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-controller-manager-role + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard-e2e +rules: + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - get + - list + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/status + verbs: + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-proxy-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-proxy-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: catalogd-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard-e2e +rules: + - apiGroups: + - "" + resources: + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-controller-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard-e2e +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-leader-election-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-leader-election-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/service-olmv1-system-catalogd-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service + namespace: olmv1-system +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + - name: webhook + port: 9443 + protocol: TCP + targetPort: 9443 + - name: metrics + port: 7443 + protocol: TCP + targetPort: 7443 + selector: + app.kubernetes.io/name: catalogd +--- +# Source: olmv1/templates/service-olmv1-system-operator-controller-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-service + namespace: olmv1-system +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: operator-controller +--- +# Source: olmv1/templates/e2e/pod-olmv1-system-e2e-coverage-copy-pod.yml +apiVersion: v1 +kind: Pod +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: e2e + app.kubernetes.io/part-of: olm + name: e2e-coverage-copy-pod + namespace: olmv1-system +spec: + containers: + - command: + - sleep + - infinity + image: busybox:1.36 + name: tar + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + terminationMessagePolicy: FallbackToLogsOnError + volumeMounts: + - mountPath: /e2e-coverage + name: e2e-coverage-volume + readOnly: true + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + volumes: + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + readOnly: true +--- +# Source: olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + minReadySeconds: 5 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: catalogd-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + control-plane: catalogd-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --leader-elect + - --metrics-bind-address=:7443 + - --external-address=catalogd-service.olmv1-system.svc + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --pull-cas-dir=/var/ca-certs + - --tls-profile=custom + - --tls-custom-version=TLSv1.3 + - --tls-custom-curves=X25519,prime256v1 + - --tls-custom-ciphers=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384 + command: + - ./catalogd + env: + - name: GOCOVERDIR + value: /e2e-coverage + image: "quay.io/operator-framework/catalogd:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /e2e-coverage + name: e2e-coverage-volume + - mountPath: /var/cache/ + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: catalogd-controller-manager + volumes: + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogd-service-cert-git-version + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: catalogd-service-cert-git-version + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: operator-controller-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: operator-controller + control-plane: operator-controller-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + - --leader-elect + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --pull-cas-dir=/var/ca-certs + - --tls-profile=modern + command: + - /operator-controller + env: + - name: GOCOVERDIR + value: /e2e-coverage + image: "quay.io/operator-framework/operator-controller:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + - mountPath: /etc/containers + name: e2e-registries-conf + - mountPath: /e2e-coverage + name: e2e-coverage-volume + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: operator-controller-certs + readOnly: true + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: operator-controller-controller-manager + volumes: + - configMap: + name: e2e-registries-conf + name: e2e-registries-conf + - name: e2e-coverage-volume + persistentVolumeClaim: + claimName: e2e-coverage + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: operator-controller-cert + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca + namespace: cert-manager +spec: + commonName: olmv1-ca + isCA: true + issuerRef: + group: cert-manager.io + kind: Issuer + name: self-sign-issuer + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: olmv1-ca + secretTemplate: + annotations: + cert-manager.io/allow-direct-injection: "true" +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service-cert + namespace: olmv1-system +spec: + dnsNames: + - localhost + - catalogd-service.olmv1-system.svc + - catalogd-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: catalogd-service-cert-git-version +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: operator-controller-cert + namespace: olmv1-system +spec: + dnsNames: + - operator-controller-service.olmv1-system.svc + - operator-controller-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: operator-controller-cert +--- +# Source: olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca +spec: + ca: + secretName: olmv1-ca +--- +# Source: olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + annotations: + olm.operatorframework.io/feature-set: standard-e2e + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: self-sign-issuer + namespace: cert-manager +spec: + selfSigned: {} +--- +# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: catalogd-mutating-webhook-configuration + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + cert-manager.io/inject-ca-from-secret: cert-manager/olmv1-ca + olm.operatorframework.io/feature-set: standard-e2e +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: catalogd-service + namespace: olmv1-system + path: /mutate-olm-operatorframework-io-v1-clustercatalog + port: 9443 + failurePolicy: Fail + name: inject-metadata-name.olm.operatorframework.io + rules: + - apiGroups: + - olm.operatorframework.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - clustercatalogs + sideEffects: None + timeoutSeconds: 10 + matchConditions: + - name: MissingOrIncorrectMetadataNameLabel + expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" diff --git a/manifests/standard.yaml b/manifests/standard.yaml new file mode 100644 index 0000000000..95e400c264 --- /dev/null +++ b/manifests/standard.yaml @@ -0,0 +1,2094 @@ +--- +# Source: olmv1/templates/namespace.yml +apiVersion: v1 +kind: Namespace +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + pod-security.kubernetes.io/audit: restricted + pod-security.kubernetes.io/audit-version: latest + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/enforce-version: latest + pod-security.kubernetes.io/warn: restricted + pod-security.kubernetes.io/warn-version: latest + app.kubernetes.io/part-of: olm + name: olmv1-system +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-catalogd-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 7443 + protocol: TCP + - port: 8443 + protocol: TCP + - port: 9443 + protocol: TCP + podSelector: + matchLabels: + control-plane: catalogd-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-default-deny-all-traffic.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: default-deny-all-traffic + namespace: olmv1-system +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/networkpolicy/networkpolicy-olmv1-system-operator-controller-controller-manager.yml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + egress: + - {} + ingress: + - ports: + - port: 8443 + protocol: TCP + podSelector: + matchLabels: + control-plane: operator-controller-controller-manager + policyTypes: + - Ingress + - Egress +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/serviceaccount-olmv1-system-common-controller-manager.yml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/crds/customresourcedefinition-clustercatalogs.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clustercatalogs.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterCatalog + listKind: ClusterCatalogList + plural: clustercatalogs + singular: clustercatalog + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.lastUnpacked + name: LastUnpacked + type: date + - jsonPath: .status.conditions[?(@.type=="Serving")].status + name: Serving + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is the desired state of the ClusterCatalog. + spec is required. + The controller will work to ensure that the desired + catalog is unpacked and served over the catalog content HTTP server. + properties: + availabilityMode: + default: Available + description: |- + availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster. + availabilityMode is optional. + + Allowed values are "Available" and "Unavailable" and omitted. + + When omitted, the default value is "Available". + + When set to "Available", the catalog contents will be unpacked and served over the catalog content HTTP server. + Setting the availabilityMode to "Available" tells clients that they should consider this ClusterCatalog + and its contents as usable. + + When set to "Unavailable", the catalog contents will no longer be served over the catalog content HTTP server. + When set to this availabilityMode it should be interpreted the same as the ClusterCatalog not existing. + Setting the availabilityMode to "Unavailable" can be useful in scenarios where a user may not want + to delete the ClusterCatalog all together, but would still like it to be treated as if it doesn't exist. + enum: + - Unavailable + - Available + type: string + priority: + default: 0 + description: |- + priority allows the user to define a priority for a ClusterCatalog. + priority is optional. + + A ClusterCatalog's priority is used by clients as a tie-breaker between ClusterCatalogs that meet the client's requirements. + A higher number means higher priority. + + It is up to clients to decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements. + When deciding how to break the tie in this scenario, it is recommended that clients prompt their users for additional input. + + When omitted, the default priority is 0 because that is the zero value of integers. + + Negative numbers can be used to specify a priority lower than the default. + Positive numbers can be used to specify a priority higher than the default. + + The lowest possible value is -2147483648. + The highest possible value is 2147483647. + format: int32 + type: integer + source: + description: |- + source allows a user to define the source of a catalog. + A "catalog" contains information on content that can be installed on a cluster. + Providing a catalog source makes the contents of the catalog discoverable and usable by + other on-cluster components. + These on-cluster components may do a variety of things with this information, such as + presenting the content in a GUI dashboard or installing content from the catalog on the cluster. + The catalog source must contain catalog metadata in the File-Based Catalog (FBC) format. + For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs. + source is a required field. + + Below is a minimal example of a ClusterCatalogSpec that sources a catalog from an image: + + source: + type: Image + image: + ref: quay.io/operatorhubio/catalog:latest + properties: + image: + description: |- + image is used to configure how catalog contents are sourced from an OCI image. + This field is required when type is Image, and forbidden otherwise. + properties: + pollIntervalMinutes: + description: |- + pollIntervalMinutes allows the user to set the interval, in minutes, at which the image source should be polled for new content. + pollIntervalMinutes is optional. + pollIntervalMinutes can not be specified when ref is a digest-based reference. + + When omitted, the image will not be polled for new content. + minimum: 1 + type: integer + ref: + description: |- + ref allows users to define the reference to a container image containing Catalog contents. + ref is required. + ref can not be more than 1000 characters. + + A reference can be broken down into 3 parts - the domain, name, and identifier. + + The domain is typically the registry where an image is located. + It must be alphanumeric characters (lowercase and uppercase) separated by the "." character. + Hyphenation is allowed, but the domain must start and end with alphanumeric characters. + Specifying a port to use is also allowed by adding the ":" character followed by numeric values. + The port must be the last value in the domain. + Some examples of valid domain values are "registry.mydomain.io", "quay.io", "my-registry.io:8080". + + The name is typically the repository in the registry where an image is located. + It must contain lowercase alphanumeric characters separated only by the ".", "_", "__", "-" characters. + Multiple names can be concatenated with the "/" character. + The domain and name are combined using the "/" character. + Some examples of valid name values are "operatorhubio/catalog", "catalog", "my-catalog.prod". + An example of the domain and name parts of a reference being combined is "quay.io/operatorhubio/catalog". + + The identifier is typically the tag or digest for an image reference and is present at the end of the reference. + It starts with a separator character used to distinguish the end of the name and beginning of the identifier. + For a digest-based reference, the "@" character is the separator. + For a tag-based reference, the ":" character is the separator. + An identifier is required in the reference. + + Digest-based references must contain an algorithm reference immediately after the "@" separator. + The algorithm reference must be followed by the ":" character and an encoded string. + The algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the "-", "_", "+", and "." characters. + Some examples of valid algorithm values are "sha256", "sha256+b64u", "multihash+base58". + The encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters. + + Tag-based references must begin with a word character (alphanumeric + "_") followed by word characters or ".", and "-" characters. + The tag must not be longer than 127 characters. + + An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05" + An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest" + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest or a tag + rule: self.find('(@.*:)') != "" || self.find(':.*$') != + "" + - message: tag is invalid. the tag must not be more than 127 + characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').substring(1).size() <= 127 + : true) : true' + - message: tag is invalid. valid tags must begin with a word + character (alphanumeric + "_") followed by word characters + or ".", and "-" characters + rule: 'self.find(''(@.*:)'') == "" ? (self.find('':.*$'') + != "" ? self.find('':.*$'').matches('':[\\w][\\w.-]*$'') + : true) : true' + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + x-kubernetes-validations: + - message: cannot specify pollIntervalMinutes while using digest-based + image + rule: 'self.ref.find(''(@.*:)'') != "" ? !has(self.pollIntervalMinutes) + : true' + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", the ClusterCatalog content will be sourced from an OCI image. + When using an image source, the image field must be set and must be the only field defined for this type. + enum: + - Image + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + required: + - source + type: object + status: + description: |- + status contains information about the state of the ClusterCatalog such as: + - Whether or not the catalog contents are being served via the catalog content HTTP server + - Whether or not the ClusterCatalog is progressing to a new state + - A reference to the source from which the catalog contents were retrieved + properties: + conditions: + description: |- + conditions is a representation of the current state for this ClusterCatalog. + + The current condition types are Serving and Progressing. + + The Serving condition is used to represent whether or not the contents of the catalog is being served via the HTTP(S) web server. + When it has a status of True and a reason of Available, the contents of the catalog are being served. + When it has a status of False and a reason of Unavailable, the contents of the catalog are not being served because the contents are not yet available. + When it has a status of False and a reason of UserSpecifiedUnavailable, the contents of the catalog are not being served because the catalog has been intentionally marked as unavailable. + + The Progressing condition is used to represent whether or not the ClusterCatalog is progressing or is ready to progress towards a new state. + When it has a status of True and a reason of Retrying, there was an error in the progression of the ClusterCatalog that may be resolved on subsequent reconciliation attempts. + When it has a status of True and a reason of Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing. + When it has a status of False and a reason of Blocked, there was an error in the progression of the ClusterCatalog that requires manual intervention for recovery. + + In the case that the Serving condition is True with reason Available and Progressing is True with reason Retrying, the previously fetched + catalog contents are still being served via the HTTP(S) web server while we are progressing towards serving a new version of the catalog + contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes + to the contents we identify that there are updates to the contents. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastUnpacked: + description: |- + lastUnpacked represents the last time the contents of the + catalog were extracted from their source format. As an example, + when using an Image source, the OCI image will be pulled and the + image layers written to a file-system backed cache. We refer to the + act of this extraction from the source format as "unpacking". + format: date-time + type: string + resolvedSource: + description: resolvedSource contains information about the resolved + source based on the source type. + properties: + image: + description: |- + image is a field containing resolution information for a catalog sourced from an image. + This field must be set when type is Image, and forbidden otherwise. + properties: + ref: + description: |- + ref contains the resolved image digest-based reference. + The digest format is used so users can use other tooling to fetch the exact + OCI manifests that were used to extract the catalog contents. + maxLength: 1000 + type: string + x-kubernetes-validations: + - message: must start with a valid domain. valid domains must + be alphanumeric characters (lowercase and uppercase) separated + by the "." character. + rule: self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\b') + - message: a valid name is required. valid names must contain + lowercase alphanumeric characters separated only by the + ".", "_", "__", "-" characters. + rule: self.find('(\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') + != "" + - message: must end with a digest + rule: self.find('(@.*:)') != "" + - message: digest algorithm is not valid. valid algorithms + must start with an uppercase or lowercase alpha character + followed by alphanumeric characters and may contain the + "-", "_", "+", and "." characters. + rule: 'self.find(''(@.*:)'') != "" ? self.find(''(@.*:)'').matches(''(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])'') + : true' + - message: digest is not valid. the encoded string must be + at least 32 characters + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').substring(1).size() + >= 32 : true' + - message: digest is not valid. the encoded string must only + contain hex characters (A-F, a-f, 0-9) + rule: 'self.find(''(@.*:)'') != "" ? self.find('':.*$'').matches('':[0-9A-Fa-f]*$'') + : true' + required: + - ref + type: object + type: + description: |- + type is a reference to the type of source the catalog is sourced from. + type is required. + + The only allowed value is "Image". + + When set to "Image", information about the resolved image source will be set in the 'image' field. + enum: + - Image + type: string + required: + - image + - type + type: object + x-kubernetes-validations: + - message: image is required when source type is Image, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''Image'' ? has(self.image) + : !has(self.image)' + urls: + description: urls contains the URLs that can be used to access the + catalog. + properties: + base: + description: |- + base is a cluster-internal URL that provides endpoints for + accessing the content of the catalog. + + It is expected that clients append the path for the endpoint they wish + to access. + + Currently, only a single endpoint is served and is accessible at the path + /api/v1. + + The endpoints served for the v1 API are: + - /all - this endpoint returns the entirety of the catalog contents in the FBC format + + As the needs of users and clients of the evolve, new endpoints may be added. + maxLength: 525 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: scheme must be either http or https + rule: 'isURL(self) ? (url(https://github.com/self).getScheme() == "http" || url(https://github.com/self).getScheme() + == "https") : true' + required: + - base + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/crds/customresourcedefinition-clusterextensions.olm.operatorframework.io.yml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + olm.operatorframework.io/generator: standard + name: clusterextensions.olm.operatorframework.io +spec: + group: olm.operatorframework.io + names: + kind: ClusterExtension + listKind: ClusterExtensionList + plural: clusterextensions + singular: clusterextension + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.install.bundle.name + name: Installed Bundle + type: string + - jsonPath: .status.install.bundle.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: ClusterExtension is the Schema for the clusterextensions API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec is an optional field that defines the desired state + of the ClusterExtension. + properties: + install: + description: |- + install is an optional field used to configure the installation options + for the ClusterExtension such as the pre-flight check configuration. + properties: + preflight: + description: |- + preflight is an optional field that can be used to configure the checks that are + run before installation or upgrade of the content for the package specified in the packageName field. + + When specified, it replaces the default preflight configuration for install/upgrade actions. + When not specified, the default configuration will be used. + properties: + crdUpgradeSafety: + description: |- + crdUpgradeSafety is used to configure the CRD Upgrade Safety pre-flight + checks that run prior to upgrades of installed content. + + The CRD Upgrade Safety pre-flight check safeguards from unintended + consequences of upgrading a CRD, such as data loss. + properties: + enforcement: + description: |- + enforcement is a required field, used to configure the state of the CRD Upgrade Safety pre-flight check. + + Allowed values are "None" or "Strict". The default value is "Strict". + + When set to "None", the CRD Upgrade Safety pre-flight check will be skipped + when performing an upgrade operation. This should be used with caution as + unintended consequences such as data loss can occur. + + When set to "Strict", the CRD Upgrade Safety pre-flight check will be run when + performing an upgrade operation. + enum: + - None + - Strict + type: string + required: + - enforcement + type: object + required: + - crdUpgradeSafety + type: object + x-kubernetes-validations: + - message: at least one of [crdUpgradeSafety] are required when + preflight is specified + rule: has(self.crdUpgradeSafety) + type: object + x-kubernetes-validations: + - message: at least one of [preflight] are required when install is + specified + rule: has(self.preflight) + namespace: + description: |- + namespace is a reference to a Kubernetes namespace. + This is the namespace in which the provided ServiceAccount must exist. + It also designates the default namespace where namespace-scoped resources + for the extension are applied to the cluster. + Some extensions may contain namespace-scoped resources to be applied in other namespaces. + This namespace must exist. + + namespace is required, immutable, and follows the DNS label standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters or hyphens (-), + start and end with an alphanumeric character, and be no longer than 63 characters + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 63 + type: string + x-kubernetes-validations: + - message: namespace is immutable + rule: self == oldSelf + - message: namespace must be a valid DNS1123 label + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") + serviceAccount: + description: |- + serviceAccount is a reference to a ServiceAccount used to perform all interactions + with the cluster that are required to manage the extension. + The ServiceAccount must be configured with the necessary permissions to perform these interactions. + The ServiceAccount must exist in the namespace referenced in the spec. + serviceAccount is required. + properties: + name: + description: |- + name is a required, immutable reference to the name of the ServiceAccount + to be used for installation and management of the content for the package + specified in the packageName field. + + This ServiceAccount must exist in the installNamespace. + + name follows the DNS subdomain standard as defined in [RFC 1123]. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-serviceaccount + - 123-serviceaccount + - 1-serviceaccount-2 + - someserviceaccount + - some.serviceaccount + + Some examples of invalid values are: + - -some-serviceaccount + - some-serviceaccount- + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf + - message: name must be a valid DNS1123 subdomain. It must contain + only lowercase alphanumeric characters, hyphens (-) or periods + (.), start and end with an alphanumeric character, and be + no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + required: + - name + type: object + source: + description: |- + source is a required field which selects the installation source of content + for this ClusterExtension. Selection is performed by setting the sourceType. + + Catalog is currently the only implemented sourceType, and setting the + sourcetype to "Catalog" requires the catalog field to also be defined. + + Below is a minimal example of a source definition (in yaml): + + source: + sourceType: Catalog + catalog: + packageName: example-package + properties: + catalog: + description: |- + catalog is used to configure how information is sourced from a catalog. + This field is required when sourceType is "Catalog", and forbidden otherwise. + properties: + channels: + description: |- + channels is an optional reference to a set of channels belonging to + the package specified in the packageName field. + + A "channel" is a package-author-defined stream of updates for an extension. + + Each channel in the list must follow the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. No more than 256 channels can be specified. + + When specified, it is used to constrain the set of installable bundles and + the automated upgrade path. This constraint is an AND operation with the + version field. For example: + - Given channel is set to "foo" + - Given version is set to ">=1.0.0, <1.5.0" + - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable + - Automatic upgrades will be constrained to upgrade edges defined by the selected channel + + When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths. + + Some examples of valid values are: + - 1.1.x + - alpha + - stable + - stable-v1 + - v1-stable + - dev-preview + - preview + - community + + Some examples of invalid values are: + - -some-channel + - some-channel- + - thisisareallylongchannelnamethatisgreaterthanthemaximumlength + - original_40 + - --default-channel + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + items: + maxLength: 253 + type: string + x-kubernetes-validations: + - message: channels entries must be valid DNS1123 subdomains + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + maxItems: 256 + type: array + packageName: + description: |- + packageName is a reference to the name of the package to be installed + and is used to filter the content from catalogs. + + packageName is required, immutable, and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + + Some examples of valid values are: + - some-package + - 123-package + - 1-package-2 + - somepackage + + Some examples of invalid values are: + - -some-package + - some-package- + - thisisareallylongpackagenamethatisgreaterthanthemaximumlength + - some.package + + [RFC 1123]: https://tools.ietf.org/html/rfc1123 + maxLength: 253 + type: string + x-kubernetes-validations: + - message: packageName is immutable + rule: self == oldSelf + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + selector: + description: |- + selector is an optional field that can be used + to filter the set of ClusterCatalogs used in the bundle + selection process. + + When unspecified, all ClusterCatalogs will be used in + the bundle selection process. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + upgradeConstraintPolicy: + default: CatalogProvided + description: |- + upgradeConstraintPolicy is an optional field that controls whether + the upgrade path(s) defined in the catalog are enforced for the package + referenced in the packageName field. + + Allowed values are: "CatalogProvided" or "SelfCertified", or omitted. + + When this field is set to "CatalogProvided", automatic upgrades will only occur + when upgrade constraints specified by the package author are met. + + When this field is set to "SelfCertified", the upgrade constraints specified by + the package author are ignored. This allows for upgrades and downgrades to + any version of the package. This is considered a dangerous operation as it + can lead to unknown and potentially disastrous outcomes, such as data + loss. It is assumed that users have independently verified changes when + using this option. + + When this field is omitted, the default value is "CatalogProvided". + enum: + - CatalogProvided + - SelfCertified + type: string + version: + description: |- + version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed. + + Acceptable version ranges are no longer than 64 characters. + Version ranges are composed of comma- or space-delimited values and one or + more comparison operators, known as comparison strings. Additional + comparison strings can be added using the OR operator (||). + + # Range Comparisons + + To specify a version range, you can use a comparison string like ">=3.0, + <3.6". When specifying a range, automatic updates will occur within that + range. The example comparison string means "install any version greater than + or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any + upgrades are available within the version range after initial installation, + those upgrades should be automatically performed. + + # Pinned Versions + + To specify an exact version to install you can use a version range that + "pins" to a specific version. When pinning to a specific version, no + automatic updates will occur. An example of a pinned version range is + "0.6.0", which means "only install version 0.6.0 and never + upgrade from this version". + + # Basic Comparison Operators + + The basic comparison operators and their meanings are: + - "=", equal (not aliased to an operator) + - "!=", not equal + - "<", less than + - ">", greater than + - ">=", greater than OR equal to + - "<=", less than OR equal to + + # Wildcard Comparisons + + You can use the "x", "X", and "*" characters as wildcard characters in all + comparison operations. Some examples of using the wildcard characters: + - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0" + - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0" + - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3" + - "x", "X", and "*" is equivalent to ">= 0.0.0" + + # Patch Release Comparisons + + When you want to specify a minor version up to the next major version you + can use the "~" character to perform patch comparisons. Some examples: + - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0" + - "~1" and "~1.x" is equivalent to ">=1, <2" + - "~2.3" is equivalent to ">=2.3, <2.4" + - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0" + + # Major Release Comparisons + + You can use the "^" character to make major release comparisons after a + stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples: + - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0" + - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0" + - "^2.3" is equivalent to ">=2.3, <3" + - "^2.x" is equivalent to ">=2.0.0, <3" + - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0" + - "^0.2" is equivalent to ">=0.2.0, <0.3.0" + - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4" + - "^0.0" is equivalent to ">=0.0.0, <0.1.0" + - "^0" is equivalent to ">=0.0.0, <1.0.0" + + # OR Comparisons + You can use the "||" character to represent an OR operation in the version + range. Some examples: + - ">=1.2.3, <2.0.0 || >3.0.0" + - "^0 || ^3 || ^5" + + For more information on semver, please see https://semver.org/ + maxLength: 64 + type: string + x-kubernetes-validations: + - message: invalid version expression + rule: self.matches("^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|[x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*]))?(\\.(0|[1-9]\\d*|x|X|\\*))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)((?:\\s+|,\\s*|\\s*\\|\\|\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)\\s*(v?(0|[1-9]\\d*|x|X|\\*])(\\.(0|[1-9]\\d*|x|X|\\*))?(\\.(0|[1-9]\\d*|x|X|\\*]))?(-([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?(\\+([0-9A-Za-z\\-]+(\\.[0-9A-Za-z\\-]+)*))?)\\s*)*$") + required: + - packageName + type: object + sourceType: + description: |- + sourceType is a required reference to the type of install source. + + Allowed values are "Catalog" + + When this field is set to "Catalog", information for determining the + appropriate bundle of content to install will be fetched from + ClusterCatalog resources existing on the cluster. + When using the Catalog sourceType, the catalog field must also be set. + enum: + - Catalog + type: string + required: + - sourceType + type: object + x-kubernetes-validations: + - message: catalog is required when sourceType is Catalog, and forbidden + otherwise + rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ? + has(self.catalog) : !has(self.catalog)' + required: + - namespace + - serviceAccount + - source + type: object + status: + description: status is an optional field that defines the observed state + of the ClusterExtension. + properties: + conditions: + description: |- + The set of condition types which apply to all spec.source variations are Installed and Progressing. + + The Installed condition represents whether or not the bundle has been installed for this ClusterExtension. + When Installed is True and the Reason is Succeeded, the bundle has been successfully installed. + When Installed is False and the Reason is Failed, the bundle has failed to install. + + The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state. + When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state. + When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts. + When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery. + + When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition. + These are indications from a package owner to guide users away from a particular package, channel, or bundle. + BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog. + ChannelDeprecated is set if the requested channel is marked deprecated in the catalog. + PackageDeprecated is set if the requested package is marked deprecated in the catalog. + Deprecated is a rollup condition that is present when any of the deprecated conditions are present. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + install: + description: install is a representation of the current installation + status for this ClusterExtension. + properties: + bundle: + description: |- + bundle is a required field which represents the identifying attributes of a bundle. + + A "bundle" is a versioned set of content that represents the resources that + need to be applied to a cluster to install a package. + properties: + name: + description: |- + name is required and follows the DNS subdomain standard + as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric character, + and be no longer than 253 characters. + type: string + x-kubernetes-validations: + - message: packageName must be a valid DNS1123 subdomain. + It must contain only lowercase alphanumeric characters, + hyphens (-) or periods (.), start and end with an alphanumeric + character, and be no longer than 253 characters + rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + version: + description: |- + version is a required field and is a reference to the version that this bundle represents + version follows the semantic versioning standard as defined in https://semver.org/. + type: string + x-kubernetes-validations: + - message: version must be well-formed semver + rule: self.matches("^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?(-([-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*))?(\\+([-0-9A-Za-z]+(-\\.[-0-9A-Za-z]+)*))?") + required: + - name + - version + type: object + required: + - bundle + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: olmv1/templates/rbac/clusterrole-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: catalogd-manager-role + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs/status + verbs: + - get + - patch + - update +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-metrics-reader.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-metrics-reader +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-common-proxy-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-role +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-clusterextension-viewer-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-clusterextension-viewer-role +rules: + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-controller-manager-role + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard +rules: + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - apiGroups: + - olm.operatorframework.io + resources: + - clustercatalogs + verbs: + - get + - list + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/finalizers + verbs: + - update + - apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions/status + verbs: + - patch + - update + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: + - list + - watch +--- +# Source: olmv1/templates/rbac/clusterrolebinding-catalogd-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: catalogd-proxy-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-common-proxy-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-proxy-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/clusterrolebinding-operator-controller-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/role-olmv1-system-catalogd-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: catalogd-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard +rules: + - apiGroups: + - "" + resources: + - secrets + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-common-leader-election-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-role + namespace: olmv1-system +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: olmv1/templates/rbac/role-olmv1-system-operator-controller-manager-role.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-controller-manager-role + namespace: olmv1-system + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + annotations: + olm.operatorframework.io/feature-set: standard +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-leader-election-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-leader-election-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-leader-election-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-leader-election-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: catalogd-manager-role +subjects: + - kind: ServiceAccount + name: catalogd-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/rbac/rolebinding-olmv1-system-common-manager-rolebinding.yml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-manager-rolebinding + namespace: olmv1-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operator-controller-manager-role +subjects: + - kind: ServiceAccount + name: operator-controller-controller-manager + namespace: olmv1-system +--- +# Source: olmv1/templates/service-olmv1-system-catalogd-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service + namespace: olmv1-system +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + - name: webhook + port: 9443 + protocol: TCP + targetPort: 9443 + - name: metrics + port: 7443 + protocol: TCP + targetPort: 7443 + selector: + app.kubernetes.io/name: catalogd +--- +# Source: olmv1/templates/service-olmv1-system-operator-controller-service.yml +apiVersion: v1 +kind: Service +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-service + namespace: olmv1-system +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + app.kubernetes.io/name: operator-controller +--- +# Source: olmv1/templates/deployment-olmv1-system-catalogd-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-controller-manager + namespace: olmv1-system +spec: + minReadySeconds: 5 + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: catalogd-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + control-plane: catalogd-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --leader-elect + - --metrics-bind-address=:7443 + - --external-address=catalogd-service.olmv1-system.svc + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --pull-cas-dir=/var/ca-certs + command: + - ./catalogd + image: "quay.io/operator-framework/catalogd:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /var/cache/ + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: catalogserver-certs + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: catalogd-controller-manager + volumes: + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: catalogserver-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: catalogd-service-cert-git-version + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: catalogd-service-cert-git-version + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/deployment-olmv1-system-operator-controller-controller-manager.yml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kubectl.kubernetes.io/default-logs-container: manager + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + app.kubernetes.io/part-of: olm + name: operator-controller-controller-manager + namespace: olmv1-system +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # Allow temporary 2 pods (1 + 1) for zero-downtime updates + maxUnavailable: 0 # Never allow pods to be unavailable during updates + selector: + matchLabels: + control-plane: operator-controller-controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: operator-controller + control-plane: operator-controller-controller-manager + app.kubernetes.io/part-of: olm + spec: + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + - --leader-elect + - --tls-cert=/var/certs/tls.crt + - --tls-key=/var/certs/tls.key + - --catalogd-cas-dir=/var/ca-certs + - --pull-cas-dir=/var/ca-certs + command: + - /operator-controller + image: "quay.io/operator-framework/operator-controller:devel" + name: manager + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 10m + memory: 64Mi + volumeMounts: + - mountPath: /var/cache + name: cache + - mountPath: /tmp + name: tmp + - mountPath: /var/certs + name: operator-controller-certs + readOnly: true + - mountPath: /var/ca-certs + name: ca-certs + readOnly: true + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + terminationMessagePolicy: FallbackToLogsOnError + serviceAccountName: operator-controller-controller-manager + volumes: + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp + - name: operator-controller-certs + secret: + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + optional: false + secretName: operator-controller-cert + - name: ca-certs + secret: + items: + - key: ca.crt + path: olm-ca.crt + optional: false + secretName: operator-controller-cert + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + nodeSelector: + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 +--- +# Source: olmv1/templates/cert-manager/certificate-cert-manager-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca + namespace: cert-manager +spec: + commonName: olmv1-ca + isCA: true + issuerRef: + group: cert-manager.io + kind: Issuer + name: self-sign-issuer + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: olmv1-ca + secretTemplate: + annotations: + cert-manager.io/allow-direct-injection: "true" +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-catalogd-service-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + name: catalogd-service-cert + namespace: olmv1-system +spec: + dnsNames: + - localhost + - catalogd-service.olmv1-system.svc + - catalogd-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: catalogd-service-cert-git-version +--- +# Source: olmv1/templates/cert-manager/certificate-olmv1-system-operator-controller-cert.yml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: operator-controller-cert + namespace: olmv1-system +spec: + dnsNames: + - operator-controller-service.olmv1-system.svc + - operator-controller-service.olmv1-system.svc.cluster.local + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: olmv1-ca + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + secretName: operator-controller-cert +--- +# Source: olmv1/templates/cert-manager/clusterissuer-olmv1-ca.yml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: olmv1-ca +spec: + ca: + secretName: olmv1-ca +--- +# Source: olmv1/templates/cert-manager/issuer-cert-manager-self-sign-issuer.yml +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + annotations: + olm.operatorframework.io/feature-set: standard + labels: + app.kubernetes.io/name: olmv1 + app.kubernetes.io/part-of: olm + name: self-sign-issuer + namespace: cert-manager +spec: + selfSigned: {} +--- +# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: catalogd-mutating-webhook-configuration + labels: + app.kubernetes.io/name: catalogd + app.kubernetes.io/part-of: olm + annotations: + cert-manager.io/inject-ca-from-secret: cert-manager/olmv1-ca + olm.operatorframework.io/feature-set: standard +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: catalogd-service + namespace: olmv1-system + path: /mutate-olm-operatorframework-io-v1-clustercatalog + port: 9443 + failurePolicy: Fail + name: inject-metadata-name.olm.operatorframework.io + rules: + - apiGroups: + - olm.operatorframework.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - clustercatalogs + sideEffects: None + timeoutSeconds: 10 + matchConditions: + - name: MissingOrIncorrectMetadataNameLabel + expression: "'name' in object.metadata && (!has(object.metadata.labels) || !('olm.operatorframework.io/metadata.name' in object.metadata.labels) || object.metadata.labels['olm.operatorframework.io/metadata.name'] != object.metadata.name)" diff --git a/mkdocs.yml b/mkdocs.yml index 69c3f015c0..e891896222 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,26 +1,59 @@ # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json -site_name: Operator Controller documentation +site_name: Operator Lifecycle Manager theme: - name: "material" - features: - - content.code.copy + logo: assets/logo.svg + name: "material" + palette: + primary: black + features: + - content.code.copy + - navigation.top +# - navigation.tabs + - navigation.indexes repo_url: https://github.com/operator-framework/operator-controller +extra_css: + - css/extra.css + nav: - - Home: 'index.md' - - Components: 'components.md' - - Tasks: - - Adding a catalog of extensions: 'Tasks/adding-a-catalog.md' - - Finding extensions to install: 'Tasks/exploring-available-packages.md' - - Installing an extension: 'Tasks/installing-an-extension.md' - - Deleting an extension: 'Tasks/uninstalling-an-extension.md' - - References: - - API references: - - Operator controller API reference: 'refs/api/operator-controller-api-reference.md' - - Catalog queries: 'refs/catalog-queries.md' - - CRD Upgrade Safety: 'refs/crd-upgrade-safety.md' + - Overview: + - index.md + - Community: project/olmv1_community.md + - Architecture: project/olmv1_architecture.md + - Design Decisions: project/olmv1_design_decisions.md + - Limitations: project/olmv1_limitations.md + - Roadmap: project/olmv1_roadmap.md + - Public API: project/public-api.md + - Getting Started: getting-started/olmv1_getting_started.md + - Tutorials: + - Add a Catalog: tutorials/add-catalog.md + - Explore Content: tutorials/explore-available-content.md + - Install an Extension: tutorials/install-extension.md + - Upgrade an Extension: tutorials/upgrade-extension.md + - Downgrade an Extension: tutorials/downgrade-extension.md + - Uninstall an Extension: tutorials/uninstall-extension.md + - How-To Guides: + - Catalog queries: howto/catalog-queries.md + - Channel-Based Upgrades: howto/how-to-channel-based-upgrades.md + - Version Pinning: howto/how-to-pin-version.md + - Version Range Upgrades: howto/how-to-version-range-upgrades.md + - Z-Stream Upgrades: howto/how-to-z-stream-upgrades.md + - Derive Service Account Permissions: howto/derive-service-account.md + - Grant Access to Your Extension's API: howto/how-to-grant-api-access.md + - Conceptual Guides: + - Single Owner Objects: concepts/single-owner-objects.md + - Upgrade Support: concepts/upgrade-support.md + - CRD Upgrade Safety: concepts/crd-upgrade-safety.md + - Content Resolution: concepts/controlling-catalog-selection.md + - Version Ranges: concepts/version-ranges.md + - API Reference: + - OLMv1 API reference: api-reference/olmv1-api-reference.md + - CatalogD Web Server reference: api-reference/catalogd-webserver.md + - Contribute: + - Contributing: contribute/contributing.md + - Developing OLM v1: contribute/developer.md markdown_extensions: - pymdownx.highlight: @@ -29,7 +62,11 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - def_list - pymdownx.tasklist: custom_checkbox: true diff --git a/requirements.txt b/requirements.txt index 64df72806a..01e061a3c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,35 +1,35 @@ -Babel==2.16.0 -beautifulsoup4==4.12.3 -certifi==2024.8.30 -charset-normalizer==3.3.2 -click==8.1.7 +Babel==2.17.0 +beautifulsoup4==4.14.2 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.3.0 colorama==0.4.6 -cssselect==1.2.0 +cssselect==1.3.0 ghp-import==2.1.0 -idna==3.8 -Jinja2==3.1.4 -lxml==5.3.0 -Markdown==3.7 -markdown2==2.5.0 -MarkupSafe==2.1.5 +idna==3.11 +Jinja2==3.1.6 +lxml==6.0.2 +Markdown==3.10 +markdown2==2.5.4 +MarkupSafe==3.0.3 mergedeep==1.3.4 mkdocs==1.6.1 -mkdocs-material==9.5.34 +mkdocs-material==9.6.23 mkdocs-material-extensions==1.3.1 -packaging==24.1 +packaging==25.0 paginate==0.5.7 pathspec==0.12.1 -platformdirs==4.3.2 -Pygments==2.18.0 -pymdown-extensions==10.9 +platformdirs==4.5.0 +Pygments==2.19.2 +pymdown-extensions==10.16.1 pyquery==2.0.1 python-dateutil==2.9.0.post0 -PyYAML==6.0.2 -pyyaml_env_tag==0.1 +PyYAML==6.0.3 +pyyaml_env_tag==1.1 readtime==3.0.0 -regex==2024.7.24 -requests==2.32.3 -six==1.16.0 -soupsieve==2.6 -urllib3==2.2.2 -watchdog==4.0.2 +regex==2025.11.3 +requests==2.32.5 +six==1.17.0 +soupsieve==2.8 +urllib3==2.5.0 +watchdog==6.0.0 diff --git a/runtime.txt b/runtime.txt index cc1923a40b..24ee5b1be9 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -3.8 +3.13 diff --git a/scripts/OWNERS b/scripts/OWNERS new file mode 100644 index 0000000000..b44dad0ea8 --- /dev/null +++ b/scripts/OWNERS @@ -0,0 +1,2 @@ +approvers: + - manifest-approvers diff --git a/scripts/install.tpl.sh b/scripts/install.tpl.sh index e4f95b4312..2ddf79856b 100644 --- a/scripts/install.tpl.sh +++ b/scripts/install.tpl.sh @@ -2,30 +2,56 @@ set -euo pipefail IFS=$'\n\t' -operator_controller_manifest=$MANIFEST +olmv1_manifest=$MANIFEST +olmv1_namespace=olmv1-system -if [[ -z "$operator_controller_manifest" ]]; then +usage() { + cmd=$(basename $0) + cat <] [-h] + +DESCRIPTION + Installs OLMv1 in the provided with cert-manager. + A kubernetes configuration must already be present. + + -n + install OLMv1 in the given . Defaults to olmv1-system. + + -h + help (this text) +EOF + exit 0 +} + + +while getopts n:h opt; do + case ${opt} in + n) olmv1_namespace=${OPTARG} ;; + h) usage ;; + *) echo "Unknown option" >&2 + exit 1 + esac +done + +if [[ -z "$olmv1_manifest" ]]; then echo "Error: Missing required MANIFEST variable" exit 1 fi -catalogd_version=$CATALOGD_VERSION +default_catalogs_manifest=$DEFAULT_CATALOG cert_mgr_version=$CERT_MGR_VERSION install_default_catalogs=$INSTALL_DEFAULT_CATALOGS -if [[ -z "$catalogd_version" || -z "$cert_mgr_version" ]]; then - err="Error: Missing component version(s) for: " - if [[ -z "$catalogd_version" ]]; then - err+="catalogd " - fi - if [[ -z "$cert_mgr_version" ]]; then - err+="cert-manager " - fi - echo "$err" +if [[ -z "$cert_mgr_version" ]]; then + echo "Error: Missing CERT_MGR_VERSION variable" exit 1 fi -function kubectl_wait() { +kubectl_wait() { namespace=$1 runtime=$2 timeout=$3 @@ -33,7 +59,7 @@ function kubectl_wait() { kubectl wait --for=condition=Available --namespace="${namespace}" "${runtime}" --timeout="${timeout}" } -function kubectl_wait_rollout() { +kubectl_wait_rollout() { namespace=$1 runtime=$2 timeout=$3 @@ -41,18 +67,53 @@ function kubectl_wait_rollout() { kubectl rollout status --namespace="${namespace}" "${runtime}" --timeout="${timeout}" } +kubectl_wait_for_query() { + manifest=$1 + query=$2 + timeout=$3 + poll_interval_in_seconds=$4 + + if [[ -z "$manifest" || -z "$query" || -z "$timeout" || -z "$poll_interval_in_seconds" ]]; then + echo "Error: Missing arguments." + echo "Usage: kubectl_wait_for_query " + exit 1 + fi + + start_time=$(date +%s) + while true; do + val=$(kubectl get "${manifest}" -o jsonpath="${query}" 2>/dev/null || echo "") + if [[ -n "${val}" ]]; then + echo "${manifest} has ${query}." + break + fi + if [[ $(( $(date +%s) - start_time )) -ge ${timeout} ]]; then + echo "Timed out waiting for ${manifest} to have ${query}." + exit 1 + fi + sleep ${poll_interval_in_seconds}s + done +} + kubectl apply -f "https://github.com/cert-manager/cert-manager/releases/download/$%7Bcert_mgr_version%7D/cert-manager.yaml" +# Wait for cert-manager to be fully ready kubectl_wait "cert-manager" "deployment/cert-manager-webhook" "60s" +kubectl_wait "cert-manager" "deployment/cert-manager-cainjector" "60s" +kubectl_wait "cert-manager" "deployment/cert-manager" "60s" +kubectl_wait_for_query "mutatingwebhookconfigurations/cert-manager-webhook" '{.webhooks[0].clientConfig.caBundle}' 60 5 +kubectl_wait_for_query "validatingwebhookconfigurations/cert-manager-webhook" '{.webhooks[0].clientConfig.caBundle}' 60 5 + +# Change the file into a file:// url +if [ -f "${olmv1_manifest}" ]; then + olmv1_manifest=file://localhost$(realpath ${olmv1_manifest}) +fi -kubectl apply -f "https://github.com/operator-framework/catalogd/releases/download/$%7Bcatalogd_version%7D/catalogd.yaml" +curl -L -s "${olmv1_manifest}" | sed "s/olmv1-system/${olmv1_namespace}/g" | kubectl apply -f - # Wait for the rollout, and then wait for the deployment to be Available -kubectl_wait_rollout "olmv1-system" "deployment/catalogd-controller-manager" "60s" -kubectl_wait "olmv1-system" "deployment/catalogd-controller-manager" "60s" +kubectl_wait_rollout "${olmv1_namespace}" "deployment/catalogd-controller-manager" "60s" +kubectl_wait "${olmv1_namespace}" "deployment/catalogd-controller-manager" "60s" +kubectl_wait "${olmv1_namespace}" "deployment/operator-controller-controller-manager" "60s" -if [[ "${install_default_catalogs,,}" != "false" ]]; then - kubectl apply -f "https://github.com/operator-framework/catalogd/releases/download/$%7Bcatalogd_version%7D/default-catalogs.yaml" - kubectl wait --for=condition=Unpacked "clustercatalog/operatorhubio" --timeout="60s" +if [[ "${install_default_catalogs}" != "false" ]]; then + kubectl apply -f "${default_catalogs_manifest}" + kubectl wait --for=condition=Serving "clustercatalog/operatorhubio" --timeout="60s" fi - -kubectl apply -f "${operator_controller_manifest}" -kubectl_wait "olmv1-system" "deployment/operator-controller-controller-manager" "60s" diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index 638539ff74..ab0bf48b1c 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -3,313 +3,223 @@ package e2e import ( "context" "fmt" - "io" "os" - "path/filepath" - "strings" "testing" "time" "github.com/google/go-containerregistry/pkg/crane" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" - kubeclient "k8s.io/client-go/kubernetes" - "k8s.io/utils/env" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/utils/ptr" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" + . "github.com/operator-framework/operator-controller/test/helpers" ) const ( - artifactName = "operator-controller-e2e" + artifactName = "operator-controller-e2e" + pollDuration = time.Minute + pollInterval = time.Second + testCatalogRefEnvVar = "CATALOG_IMG" + testCatalogName = "test-catalog" ) -var pollDuration = time.Minute -var pollInterval = time.Second - -func createServiceAccount(ctx context.Context, name types.NamespacedName, clusterExtensionName string) (*corev1.ServiceAccount, error) { - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name.Name, - Namespace: name.Namespace, - }, - } - err := c.Create(ctx, sa) - if err != nil { - return nil, err +func TestClusterExtensionInstallRegistry(t *testing.T) { + type testCase struct { + name string + packageName string } - - return sa, createClusterRoleAndBindingForSA(ctx, name.Name, sa, clusterExtensionName) -} - -func createClusterRoleAndBindingForSA(ctx context.Context, name string, sa *corev1.ServiceAccount, clusterExtensionName string) error { - cr := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, + for _, tc := range []testCase{ + { + name: "no registry configuration necessary", + packageName: "test", }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{ - "olm.operatorframework.io", - }, - Resources: []string{ - "clusterextensions/finalizers", - }, - Verbs: []string{ - "update", - }, - ResourceNames: []string{clusterExtensionName}, - }, - { - APIGroups: []string{ - "", - }, - Resources: []string{ - "secrets", // for helm - "services", - "serviceaccounts", - }, - Verbs: []string{ - "create", - "update", - "delete", - "patch", - "get", - "list", - "watch", - }, - }, - { - APIGroups: []string{ - "apiextensions.k8s.io", - }, - Resources: []string{ - "customresourcedefinitions", - }, - Verbs: []string{ - "create", - "update", - "delete", - "patch", - "get", - "list", - "watch", - }, - }, - { - APIGroups: []string{ - "apps", - }, - Resources: []string{ - "deployments", - }, - Verbs: []string{ - "create", - "update", - "delete", - "patch", - "get", - "list", - "watch", - }, - }, - { - APIGroups: []string{ - "rbac.authorization.k8s.io", - }, - Resources: []string{ - "clusterroles", - "roles", - "clusterrolebindings", - "rolebindings", + { + // NOTE: This test requires an extra configuration in /etc/containers/registries.conf, which is mounted + // for this e2e via the ./config/components/e2e/registries-conf kustomize component as part of the e2e component. + // The goal here is to prove that "mirrored-registry.operator-controller-e2e.svc.cluster.local:5000" is + // mapped to the "real" registry hostname ("docker-registry.operator-controller-e2e.svc.cluster.local:5000"). + name: "package requires mirror registry configuration in /etc/containers/registries.conf", + packageName: "test-mirrored", + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log("When a cluster extension is installed from a catalog") + t.Log("When the extension bundle format is registry+v1") + + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: tc.packageName, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, }, - Verbs: []string{ - "create", - "update", - "delete", - "patch", - "get", - "list", - "watch", - "bind", - "escalate", + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, - }, - }, - } - err := c.Create(ctx, cr) - if err != nil { - return err - } - crb := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: sa.Name, - Namespace: sa.Namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: name, - }, - } - err = c.Create(ctx, crb) - if err != nil { - return err - } - - return nil -} - -func testInit(t *testing.T) (*ocv1alpha1.ClusterExtension, *catalogd.ClusterCatalog, *corev1.ServiceAccount) { - var err error - extensionCatalog, err := createTestCatalog(context.Background(), testCatalogName, os.Getenv(testCatalogRefEnvVar)) - require.NoError(t, err) - - clusterExtensionName := fmt.Sprintf("clusterextension-%s", rand.String(8)) - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterExtensionName, - }, - } - - defaultNamespace := types.NamespacedName{ - Name: clusterExtensionName, - Namespace: "default", + } + t.Log("It resolves the specified package with correct bundle path") + t.Log("By creating the ClusterExtension resource") + require.NoError(t, c.Create(context.Background(), clusterExtension)) + + t.Log("By eventually reporting a successful resolution and bundle path") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By eventually reporting progressing as True") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By eventually installing the package successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By eventually creating the NetworkPolicy named 'test-operator-network-policy'") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + var np networkingv1.NetworkPolicy + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: "test-operator-network-policy", Namespace: ns.Name}, &np)) + }, pollDuration, pollInterval) + + t.Log("By verifying that no templating occurs for registry+v1 bundle manifests") + cm := corev1.ConfigMap{} + require.NoError(t, c.Get(context.Background(), types.NamespacedName{Namespace: ns.Name, Name: "test-configmap"}, &cm)) + require.Contains(t, cm.Annotations, "shouldNotTemplate") + require.Contains(t, cm.Annotations["shouldNotTemplate"], "{{ $labels.namespace }}") + }) } - - sa, err := createServiceAccount(context.Background(), defaultNamespace, clusterExtensionName) - require.NoError(t, err) - return clusterExtension, extensionCatalog, sa } -func testCleanup(t *testing.T, cat *catalogd.ClusterCatalog, clusterExtension *ocv1alpha1.ClusterExtension, sa *corev1.ServiceAccount) { - require.NoError(t, c.Delete(context.Background(), cat)) - require.Eventually(t, func() bool { - err := c.Get(context.Background(), types.NamespacedName{Name: cat.Name}, &catalogd.ClusterCatalog{}) - return errors.IsNotFound(err) - }, pollDuration, pollInterval) - require.NoError(t, c.Delete(context.Background(), clusterExtension)) - require.Eventually(t, func() bool { - err := c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, &ocv1alpha1.ClusterExtension{}) - return errors.IsNotFound(err) - }, pollDuration, pollInterval) - require.NoError(t, c.Delete(context.Background(), sa)) - require.Eventually(t, func() bool { - err := c.Get(context.Background(), types.NamespacedName{Name: sa.Name, Namespace: sa.Namespace}, &corev1.ServiceAccount{}) - return errors.IsNotFound(err) - }, pollDuration, pollInterval) -} +func TestClusterExtensionInstallRegistryDynamic(t *testing.T) { + // NOTE: Like 'TestClusterExtensionInstallRegistry', this test also requires extra configuration in /etc/containers/registries.conf + packageName := "dynamic" -func TestClusterExtensionInstallRegistry(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When the extension bundle format is registry+v1") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", - Selector: metav1.LabelSelector{ + Catalog: &ocv1.CatalogFilter{ + PackageName: packageName, + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } + t.Log("It updates the registries.conf file contents") + cm := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-registries-conf", + Namespace: "olmv1-system", + }, + Data: map[string]string{ + "registries.conf": `[[registry]] +prefix = "dynamic-registry.operator-controller-e2e.svc.cluster.local:5000" +location = "docker-registry.operator-controller-e2e.svc.cluster.local:5000"`, + }, + } + require.NoError(t, c.Update(context.Background(), &cm)) + t.Log("It resolves the specified package with correct bundle path") t.Log("By creating the ClusterExtension resource") require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) - }, pollDuration, pollInterval) - - t.Log("By eventually reporting a successful unpacked") + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + }, 2*time.Minute, pollInterval) + + // Give the check 2 minutes instead of the typical 1 for the pod's + // files to update from the configmap change. + // The theoretical max time is the kubelet sync period of 1 minute + + // ConfigMap cache TTL of 1 minute = 2 minutes + t.Log("By eventually reporting progressing as True") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "unpack successful") - }, pollDuration, pollInterval) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, 2*time.Minute, pollInterval) t.Log("By eventually installing the package successfully") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "Installed bundle") - assert.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) }, pollDuration, pollInterval) } func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + extraCatalog, err := CreateTestCatalog(context.Background(), "extra-test-catalog", os.Getenv(testCatalogRefEnvVar)) + require.NoError(t, err) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + defer func(cat *ocv1.ClusterCatalog) { + require.NoError(t, c.Delete(context.Background(), cat)) + require.Eventually(t, func() bool { + err := c.Get(context.Background(), types.NamespacedName{Name: cat.Name}, &ocv1.ClusterCatalog{}) + return errors.IsNotFound(err) + }, pollDuration, pollInterval) + }(extraCatalog) + + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves to multiple bundle paths") @@ -318,15 +228,17 @@ func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) { t.Log("By eventually reporting a failed resolution with multiple bundles") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionFalse, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonFailed, cond.Reason) - assert.Contains(ct, cond.Message, "in multiple catalogs with the same priority [operatorhubio test-catalog]") - assert.Nil(ct, clusterExtension.Status.Resolution) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By eventually reporting Progressing == True and Reason Retrying") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + require.Contains(ct, cond.Message, "in multiple catalogs with the same priority [extra-test-catalog test-catalog]") }, pollDuration, pollInterval) } @@ -334,51 +246,41 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) t.Log("By creating an ClusterExtension at a specified version") - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", Version: "1.0.0", // No Selector since this is an exact version match }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful installation") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.0.0", - Version: "1.0.0", - }}, - clusterExtension.Status.Resolution, - ) - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionInstallStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.0.0", + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + require.Equal(ct, + &ocv1.ClusterExtensionInstallStatus{Bundle: ocv1.BundleMetadata{ + Name: "test-operator.1.0.0", Version: "1.0.0", }}, clusterExtension.Status.Install, ) + + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) t.Log("It does not allow to upgrade the ClusterExtension to a non-successor version") @@ -388,14 +290,16 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { require.NoError(t, c.Update(context.Background(), clusterExtension)) t.Log("By eventually reporting an unsatisfiable resolution") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonFailed, cond.Reason) - assert.Equal(ct, "error upgrading from currently installed version \"1.0.0\": no bundles found for package \"prometheus\" matching version \"1.2.0\"", cond.Message) - assert.Empty(ct, clusterExtension.Status.Resolution) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By eventually reporting Progressing == True and Reason Retrying") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + require.Equal(ct, "error upgrading from currently installed version \"1.0.0\": no bundles found for package \"test\" matching version \"1.2.0\"", cond.Message) }, pollDuration, pollInterval) } @@ -403,110 +307,79 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) t.Log("By creating an ClusterExtension at a specified version") - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", Version: "1.0.0", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.0.0", - Version: "1.0.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) t.Log("It allows to upgrade the ClusterExtension to a non-successor version") t.Log("By updating the ClusterExtension resource to a non-successor version") // 1.2.0 does not replace/skip/skipRange 1.0.0. clusterExtension.Spec.Source.Catalog.Version = "1.2.0" - clusterExtension.Spec.Source.Catalog.UpgradeConstraintPolicy = ocv1alpha1.UpgradeConstraintPolicySelfCertified + clusterExtension.Spec.Source.Catalog.UpgradeConstraintPolicy = ocv1.UpgradeConstraintPolicySelfCertified require.NoError(t, c.Update(context.Background(), clusterExtension)) t.Log("By eventually reporting a satisfiable resolution") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) } func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) t.Log("By creating an ClusterExtension at a specified version") - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", Version: "1.0.0", }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.0.0", - Version: "1.0.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) t.Log("It does allow to upgrade the ClusterExtension to any of the successor versions within non-zero major version") @@ -516,36 +389,27 @@ func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { require.NoError(t, c.Update(context.Background(), clusterExtension)) t.Log("By eventually reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.0.1", - Version: "1.0.1", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) } func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("It resolves again when a catalog is patched with new ImageRef") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", - Selector: metav1.LabelSelector{ + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { Key: "olm.operatorframework.io/metadata.name", @@ -556,11 +420,9 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) { }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") @@ -569,55 +431,35 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) { t.Log("By reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) // patch imageRef tag on test-catalog image with v2 image t.Log("By patching the catalog ImageRef to point to the v2 catalog") - updatedCatalogImage := fmt.Sprintf("%s/e2e/test-catalog:v2", os.Getenv("LOCAL_REGISTRY_HOST")) + updatedCatalogImage := fmt.Sprintf("%s/e2e/test-catalog:v2", os.Getenv("CLUSTER_REGISTRY_HOST")) err := patchTestCatalog(context.Background(), testCatalogName, updatedCatalogImage) require.NoError(t, err) require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.Name}, extensionCatalog)) - cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, catalogd.TypeUnpacked) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, catalogd.ReasonUnpackSuccessful, cond.Reason) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.Name}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) }, pollDuration, pollInterval) - t.Log("By eventually reporting a successful resolution and bundle path") + t.Log("By eventually installing the package successfully") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.2.0.0", - Version: "2.0.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.Contains(ct, clusterExtension.Status.Install.Bundle.Version, "1.3.0") }, pollDuration, pollInterval) } @@ -627,40 +469,40 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { // Tag the image with the new tag var err error - v1Image := fmt.Sprintf("%s/%s", os.Getenv("CLUSTER_REGISTRY_HOST"), os.Getenv("E2E_TEST_CATALOG_V1")) + v1Image := fmt.Sprintf("%s/%s", os.Getenv("LOCAL_REGISTRY_HOST"), os.Getenv("E2E_TEST_CATALOG_V1")) err = crane.Tag(v1Image, latestImageTag, crane.Insecure) require.NoError(t, err) // create a test-catalog with latest image tag - latestCatalogImage := fmt.Sprintf("%s/e2e/test-catalog:latest", os.Getenv("LOCAL_REGISTRY_HOST")) - extensionCatalog, err := createTestCatalog(context.Background(), testCatalogName, latestCatalogImage) + latestCatalogImage := fmt.Sprintf("%s/e2e/test-catalog:latest", os.Getenv("CLUSTER_REGISTRY_HOST")) + extensionCatalog, err := CreateTestCatalog(context.Background(), testCatalogName, latestCatalogImage) require.NoError(t, err) clusterExtensionName := fmt.Sprintf("clusterextension-%s", rand.String(8)) - clusterExtension := &ocv1alpha1.ClusterExtension{ + clusterExtension := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: clusterExtensionName, }, } - sa, err := createServiceAccount(context.Background(), types.NamespacedName{Name: clusterExtensionName, Namespace: "default"}, clusterExtensionName) + ns, err := CreateNamespace(context.Background(), clusterExtensionName) + require.NoError(t, err) + sa, err := CreateServiceAccount(context.Background(), types.NamespacedName{Name: clusterExtensionName, Namespace: ns.Name}, clusterExtensionName) require.NoError(t, err) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", - Selector: metav1.LabelSelector{ + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It resolves the specified package with correct bundle path") @@ -669,80 +511,56 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { t.Log("By reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) // update tag on test-catalog image with v2 image t.Log("By updating the catalog tag to point to the v2 catalog") - v2Image := fmt.Sprintf("%s/%s", os.Getenv("CLUSTER_REGISTRY_HOST"), os.Getenv("E2E_TEST_CATALOG_V2")) + v2Image := fmt.Sprintf("%s/%s", os.Getenv("LOCAL_REGISTRY_HOST"), os.Getenv("E2E_TEST_CATALOG_V2")) err = crane.Tag(v2Image, latestImageTag, crane.Insecure) require.NoError(t, err) require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.Name}, extensionCatalog)) - cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, catalogd.TypeUnpacked) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, catalogd.ReasonUnpackSuccessful, cond.Reason) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.Name}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) }, pollDuration, pollInterval) t.Log("By eventually reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.2.0.0", - Version: "2.0.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, pollDuration, pollInterval) } func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("It resolves again when managed content is changed") - clusterExtension, extensionCatalog, sa := testInit(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", - Selector: metav1.LabelSelector{ + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, } t.Log("It installs the specified package with correct bundle path") @@ -751,264 +569,223 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T t.Log("By reporting a successful installation") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") }, pollDuration, pollInterval) t.Log("By deleting a managed resource") - prometheusService := &corev1.Service{ + testConfigMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "prometheus-operator", - Namespace: clusterExtension.Spec.Install.Namespace, + Name: "test-configmap", + Namespace: clusterExtension.Spec.Namespace, }, } - require.NoError(t, c.Delete(context.Background(), prometheusService)) + require.NoError(t, c.Delete(context.Background(), testConfigMap)) t.Log("By eventually re-creating the managed resource") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: prometheusService.Name, Namespace: prometheusService.Namespace}, prometheusService)) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: testConfigMap.Name, Namespace: testConfigMap.Namespace}, testConfigMap)) }, pollDuration, pollInterval) } -func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *testing.T) { +func TestClusterExtensionRecoversFromNoNamespaceWhenFailureFixed(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When the extension bundle format is registry+v1") - clusterExtension, extensionCatalog, _ := testInit(t) - name := rand.String(10) - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - }, - } - err := c.Create(context.Background(), sa) - require.NoError(t, err) + t.Log("By not creating the Namespace and ServiceAccount") + clusterExtension, extensionCatalog := TestInitClusterExtensionClusterCatalog(t) - defer testCleanup(t, extensionCatalog, clusterExtension, sa) - defer getArtifactsOutput(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, nil, nil) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - PackageName: "prometheus", - Selector: metav1.LabelSelector{ + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, }, }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: "default", - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: clusterExtension.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: clusterExtension.Name, }, } + t.Log("It resolves the specified package with correct bundle path") t.Log("By creating the ClusterExtension resource") require.NoError(t, c.Create(context.Background(), clusterExtension)) - t.Log("By eventually reporting a successful resolution and bundle path") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "resolved to") - assert.Equal(ct, - &ocv1alpha1.ClusterExtensionResolutionStatus{Bundle: ocv1alpha1.BundleMetadata{ - Name: "prometheus-operator.1.2.0", - Version: "1.2.0", - }}, - clusterExtension.Status.Resolution, - ) - }, pollDuration, pollInterval) - - t.Log("By eventually reporting a successful unpacked") + t.Log("By eventually reporting Progressing == True with Reason Retrying") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeUnpacked) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "unpack successful") + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) }, pollDuration, pollInterval) - t.Log("By eventually failing to install the package successfully due to insufficient ServiceAccount permissions") + t.Log("By eventually reporting Installed != True") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionFalse, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonFailed, cond.Reason) - assert.Contains(ct, cond.Message, "forbidden") + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.NotEqual(ct, metav1.ConditionTrue, cond.Status) }, pollDuration, pollInterval) - t.Log("By fixing the ServiceAccount permissions") - require.NoError(t, createClusterRoleAndBindingForSA(context.Background(), name, sa, clusterExtension.Name)) + t.Log("By creating the Namespace and ServiceAccount") + sa, ns := TestInitServiceAccountNamespace(t, clusterExtension.Name) + defer TestCleanup(t, nil, nil, sa, ns) // NOTE: In order to ensure predictable results we need to ensure we have a single // known failure with a singular fix operation. Additionally, due to the exponential // backoff of this eventually check we MUST ensure we do not touch the ClusterExtension - // after creating and binding the needed permissions to the ServiceAccount. + // after creating int the Namespace and ServiceAccount. t.Log("By eventually installing the package successfully") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "Installed bundle") - assert.NotEmpty(ct, clusterExtension.Status.Install) + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotEmpty(ct, clusterExtension.Status.Install) }, pollDuration, pollInterval) -} -// getArtifactsOutput gets all the artifacts from the test run and saves them to the artifact path. -// Currently it saves: -// - clusterextensions -// - pods logs -// - deployments -// - catalogsources -func getArtifactsOutput(t *testing.T) { - basePath := env.GetString("ARTIFACT_PATH", "") - if basePath == "" { - return - } + t.Log("By eventually reporting Progressing == True with Reason Success") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) +} - kubeClient, err := kubeclient.NewForConfig(cfg) - require.NoError(t, err) +func TestClusterExtensionRecoversFromExistingDeploymentWhenFailureFixed(t *testing.T) { + t.Log("When a cluster extension is installed from a catalog") + t.Log("When the extension bundle format is registry+v1") - // sanitize the artifact name for use as a directory name - testName := strings.ReplaceAll(strings.ToLower(t.Name()), " ", "-") - // Get the test description and sanitize it for use as a directory name - artifactPath := filepath.Join(basePath, artifactName, fmt.Sprint(time.Now().UnixNano()), testName) + clusterExtension, extensionCatalog, sa, ns := TestInit(t) - // Create the full artifact path - err = os.MkdirAll(artifactPath, 0755) - require.NoError(t, err) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - // Get all namespaces - namespaces := corev1.NamespaceList{} - if err := c.List(context.Background(), &namespaces); err != nil { - fmt.Printf("Failed to list namespaces: %v", err) + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: clusterExtension.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: clusterExtension.Name, + }, } - // get all cluster extensions save them to the artifact path. - clusterExtensions := ocv1alpha1.ClusterExtensionList{} - if err := c.List(context.Background(), &clusterExtensions, client.InNamespace("")); err != nil { - fmt.Printf("Failed to list cluster extensions: %v", err) - } - for _, clusterExtension := range clusterExtensions.Items { - // Save cluster extension to artifact path - clusterExtensionYaml, err := yaml.Marshal(clusterExtension) - if err != nil { - fmt.Printf("Failed to marshal cluster extension: %v", err) - continue - } - if err := os.WriteFile(filepath.Join(artifactPath, clusterExtension.Name+"-clusterextension.yaml"), clusterExtensionYaml, 0600); err != nil { - fmt.Printf("Failed to write cluster extension to file: %v", err) - } + t.Log("By creating a new Deployment that can not be adopted") + newDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator", + Namespace: clusterExtension.Name, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test-operator"}, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"app": "test-operator"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Command: []string{"sleep", "1000"}, + Image: "busybox", + ImagePullPolicy: corev1.PullAlways, + Name: "busybox", + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To(int64(1000)), + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + }, + }, + }, } + require.NoError(t, c.Create(context.Background(), newDeployment)) - // get all catalogsources save them to the artifact path. - catalogsources := catalogd.ClusterCatalogList{} - if err := c.List(context.Background(), &catalogsources, client.InNamespace("")); err != nil { - fmt.Printf("Failed to list catalogsources: %v", err) - } - for _, catalogsource := range catalogsources.Items { - // Save catalogsource to artifact path - catalogsourceYaml, err := yaml.Marshal(catalogsource) - if err != nil { - fmt.Printf("Failed to marshal catalogsource: %v", err) - continue - } - if err := os.WriteFile(filepath.Join(artifactPath, catalogsource.Name+"-catalogsource.yaml"), catalogsourceYaml, 0600); err != nil { - fmt.Printf("Failed to write catalogsource to file: %v", err) - } - } + t.Log("It resolves the specified package with correct bundle path") + t.Log("By creating the ClusterExtension resource") + require.NoError(t, c.Create(context.Background(), clusterExtension)) - for _, namespace := range namespaces.Items { - // let's ignore kube-* namespaces. - if strings.Contains(namespace.Name, "kube-") { - continue - } - - namespacedArtifactPath := filepath.Join(artifactPath, namespace.Name) - if err := os.Mkdir(namespacedArtifactPath, 0755); err != nil { - fmt.Printf("Failed to create namespaced artifact path: %v", err) - continue - } - - // get all deployments in the namespace and save them to the artifact path. - deployments := appsv1.DeploymentList{} - if err := c.List(context.Background(), &deployments, client.InNamespace(namespace.Name)); err != nil { - fmt.Printf("Failed to list deployments %v in namespace: %q", err, namespace.Name) - continue - } - - for _, deployment := range deployments.Items { - // Save deployment to artifact path - deploymentYaml, err := yaml.Marshal(deployment) - if err != nil { - fmt.Printf("Failed to marshal deployment: %v", err) - continue - } - if err := os.WriteFile(filepath.Join(namespacedArtifactPath, deployment.Name+"-deployment.yaml"), deploymentYaml, 0600); err != nil { - fmt.Printf("Failed to write deployment to file: %v", err) - } - } - - // Get logs from all pods in all namespaces - pods := corev1.PodList{} - if err := c.List(context.Background(), &pods, client.InNamespace(namespace.Name)); err != nil { - fmt.Printf("Failed to list pods %v in namespace: %q", err, namespace.Name) - } - for _, pod := range pods.Items { - if pod.Status.Phase != corev1.PodRunning && pod.Status.Phase != corev1.PodSucceeded && pod.Status.Phase != corev1.PodFailed { - continue - } - for _, container := range pod.Spec.Containers { - logs, err := kubeClient.CoreV1().Pods(namespace.Name).GetLogs(pod.Name, &corev1.PodLogOptions{Container: container.Name}).Stream(context.Background()) - if err != nil { - fmt.Printf("Failed to get logs for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) - continue - } - defer logs.Close() - - outFile, err := os.Create(filepath.Join(namespacedArtifactPath, pod.Name+"-"+container.Name+"-logs.txt")) - if err != nil { - fmt.Printf("Failed to create file for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) - continue - } - defer outFile.Close() - - if _, err := io.Copy(outFile, logs); err != nil { - fmt.Printf("Failed to copy logs for pod %q in namespace %q: %v", pod.Name, namespace.Name, err) - continue - } - } - } - } + t.Log("By eventually reporting Progressing == True with Reason Retrying") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By eventually failing to install the package successfully due to no adoption support") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionFalse, cond.Status) + // TODO: We probably _should_ be testing the reason here, but helm and boxcutter applier have different reasons. + // Maybe we change helm to use "Absent" rather than "Failed" since the Progressing condition already captures + // the failure? + //require.Equal(ct, ocv1.ReasonFailed, cond.Reason) + require.Contains(ct, cond.Message, "No bundle installed") + }, pollDuration, pollInterval) + + t.Log("By deleting the new Deployment") + require.NoError(t, c.Delete(context.Background(), newDeployment)) + + // NOTE: In order to ensure predictable results we need to ensure we have a single + // known failure with a singular fix operation. Additionally, due to the exponential + // backoff of this eventually check we MUST ensure we do not touch the ClusterExtension + // after deleting the Deployment. + t.Log("By eventually installing the package successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotEmpty(ct, clusterExtension.Status.Install) + }, pollDuration, pollInterval) + + t.Log("By eventually reporting Progressing == True with Reason Success") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) } diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 539260e983..aa033a2f1e 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -2,19 +2,19 @@ package e2e import ( "context" + "fmt" "os" "testing" - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - - "github.com/operator-framework/operator-controller/internal/scheme" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" ) var ( @@ -23,46 +23,31 @@ var ( ) const ( - testCatalogRefEnvVar = "CATALOG_IMG" - testCatalogName = "test-catalog" - latestImageTag = "latest" + testSummaryOutputEnvVar = "E2E_SUMMARY_OUTPUT" + latestImageTag = "latest" ) func TestMain(m *testing.M) { cfg = ctrl.GetConfigOrDie() var err error + utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme)) c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) utilruntime.Must(err) - os.Exit(m.Run()) -} - -// createTestCatalog will create a new catalog on the test cluster, provided -// the context, catalog name, and the image reference. It returns the created catalog -// or an error if any errors occurred while creating the catalog. -// Note that catalogd will automatically create the label: -// -// "olm.operatorframework.io/metadata.name": name -func createTestCatalog(ctx context.Context, name string, imageRef string) (*catalogd.ClusterCatalog, error) { - catalog := &catalogd.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: catalogd.ClusterCatalogSpec{ - Source: catalogd.CatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ImageSource{ - Ref: imageRef, - InsecureSkipTLSVerify: true, - PollInterval: &metav1.Duration{Duration: time.Second}, - }, - }, - }, + res := m.Run() + path := os.Getenv(testSummaryOutputEnvVar) + if path == "" { + fmt.Printf("Note: E2E_SUMMARY_OUTPUT is unset; skipping summary generation") + } else { + err = utils.PrintSummary(path) + if err != nil { + // Fail the run if alerts are found + fmt.Printf("%v", err) + os.Exit(1) + } } - - err := c.Create(ctx, catalog) - return catalog, err + os.Exit(res) } // patchTestCatalog will patch the existing clusterCatalog on the test cluster, provided @@ -70,7 +55,7 @@ func createTestCatalog(ctx context.Context, name string, imageRef string) (*cata // if any errors occurred while updating the catalog. func patchTestCatalog(ctx context.Context, name string, newImageRef string) error { // Fetch the existing ClusterCatalog - catalog := &catalogd.ClusterCatalog{} + catalog := &ocv1.ClusterCatalog{} err := c.Get(ctx, client.ObjectKey{Name: name}, catalog) if err != nil { return err diff --git a/test/e2e/metrics_test.go b/test/e2e/metrics_test.go new file mode 100644 index 0000000000..e41827987d --- /dev/null +++ b/test/e2e/metrics_test.go @@ -0,0 +1,253 @@ +// Package e2e contains end-to-end tests to verify that the metrics endpoints +// for both components. Metrics are exported and accessible by authorized users through +// RBAC and ServiceAccount tokens. +// +// These tests perform the following steps: +// 1. Create a ClusterRoleBinding to grant necessary permissions for accessing metrics. +// 2. Generate a ServiceAccount token for authentication. +// 3. Deploy a curl pod to interact with the metrics endpoint. +// 4. Wait for the curl pod to become ready. +// 5. Execute a curl command from the pod to validate the metrics endpoint. +// 6. Clean up all resources created during the test, such as the ClusterRoleBinding and curl pod. +// +//nolint:gosec +package e2e + +import ( + "bytes" + "context" + "fmt" + "io" + "os/exec" + "testing" + "time" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/rand" + + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" +) + +// TestOperatorControllerMetricsExportedEndpoint verifies that the metrics endpoint for the operator controller +func TestOperatorControllerMetricsExportedEndpoint(t *testing.T) { + client := utils.FindK8sClient(t) + curlNamespace := createRandomNamespace(t, client) + componentNamespace := getComponentNamespace(t, client, "control-plane=operator-controller-controller-manager") + metricsURL := fmt.Sprintf("https://operator-controller-service.%s.svc.cluster.local:8443/metrics", componentNamespace) + + config := NewMetricsTestConfig( + client, + curlNamespace, + "operator-controller-metrics-reader", + "operator-controller-metrics-binding", + "operator-controller-metrics-reader", + "oper-curl-metrics", + metricsURL, + ) + + config.run(t) +} + +// TestCatalogdMetricsExportedEndpoint verifies that the metrics endpoint for catalogd +func TestCatalogdMetricsExportedEndpoint(t *testing.T) { + client := utils.FindK8sClient(t) + curlNamespace := createRandomNamespace(t, client) + componentNamespace := getComponentNamespace(t, client, "control-plane=catalogd-controller-manager") + metricsURL := fmt.Sprintf("https://catalogd-service.%s.svc.cluster.local:7443/metrics", componentNamespace) + + config := NewMetricsTestConfig( + client, + curlNamespace, + "catalogd-metrics-reader", + "catalogd-metrics-binding", + "catalogd-metrics-reader", + "catalogd-curl-metrics", + metricsURL, + ) + + config.run(t) +} + +// MetricsTestConfig holds the necessary configurations for testing metrics endpoints. +type MetricsTestConfig struct { + client string + namespace string + clusterRole string + clusterBinding string + serviceAccount string + curlPodName string + metricsURL string +} + +// NewMetricsTestConfig initializes a new MetricsTestConfig. +func NewMetricsTestConfig(client, namespace, clusterRole, clusterBinding, serviceAccount, curlPodName, metricsURL string) *MetricsTestConfig { + return &MetricsTestConfig{ + client: client, + namespace: namespace, + clusterRole: clusterRole, + clusterBinding: clusterBinding, + serviceAccount: serviceAccount, + curlPodName: curlPodName, + metricsURL: metricsURL, + } +} + +// run will execute all steps of those tests +func (c *MetricsTestConfig) run(t *testing.T) { + defer c.cleanup(t) + + c.createMetricsClusterRoleBinding(t) + token := c.getServiceAccountToken(t) + c.createCurlMetricsPod(t) + c.validate(t, token) +} + +// createMetricsClusterRoleBinding to binding and expose the metrics +func (c *MetricsTestConfig) createMetricsClusterRoleBinding(t *testing.T) { + t.Logf("Creating ClusterRoleBinding %s for %s in namespace %s", c.clusterBinding, c.serviceAccount, c.namespace) + cmd := exec.Command(c.client, "create", "clusterrolebinding", c.clusterBinding, + "--clusterrole="+c.clusterRole, + "--serviceaccount="+c.namespace+":"+c.serviceAccount) + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Error creating ClusterRoleBinding: %s", string(output)) +} + +// getServiceAccountToken return the token requires to have access to the metrics +func (c *MetricsTestConfig) getServiceAccountToken(t *testing.T) string { + t.Logf("Creating ServiceAccount %q in namespace %q", c.serviceAccount, c.namespace) + output, err := exec.Command(c.client, "create", "serviceaccount", c.serviceAccount, "--namespace="+c.namespace).CombinedOutput() + require.NoError(t, err, "Error creating service account: %v", string(output)) + + t.Logf("Generating ServiceAccount token for %q in namespace %q", c.serviceAccount, c.namespace) + cmd := exec.Command(c.client, "create", "token", c.serviceAccount, "--namespace", c.namespace) + tokenOutput, tokenCombinedOutput, err := stdoutAndCombined(cmd) + require.NoError(t, err, "Error creating token: %s", string(tokenCombinedOutput)) + return string(bytes.TrimSpace(tokenOutput)) +} + +// createCurlMetricsPod creates the Pod with curl image to allow check if the metrics are working +func (c *MetricsTestConfig) createCurlMetricsPod(t *testing.T) { + t.Logf("Creating curl pod (%s/%s) to validate the metrics endpoint", c.namespace, c.curlPodName) + cmd := exec.Command(c.client, "run", c.curlPodName, + "--image=quay.io/curl/curl:8.15.0", + "--namespace", c.namespace, + "--restart=Never", + "--overrides", `{ + "spec": { + "terminationGradePeriodSeconds": 0, + "containers": [{ + "name": "curl", + "image": "quay.io/curl/curl:8.15.0", + "command": ["sh", "-c", "sleep 3600"], + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": {"drop": ["ALL"]}, + "runAsNonRoot": true, + "runAsUser": 1000, + "seccompProfile": {"type": "RuntimeDefault"} + } + }], + "serviceAccountName": "`+c.serviceAccount+`" + } + }`) + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Error creating curl pod: %s", string(output)) +} + +// validate verifies if is possible to access the metrics +func (c *MetricsTestConfig) validate(t *testing.T, token string) { + t.Log("Waiting for the curl pod to be ready") + waitCmd := exec.Command(c.client, "wait", "--for=condition=Ready", "pod", c.curlPodName, "--namespace", c.namespace, "--timeout=60s") + waitOutput, waitErr := waitCmd.CombinedOutput() + require.NoError(t, waitErr, "Error waiting for curl pod to be ready: %s", string(waitOutput)) + + t.Log("Validating the metrics endpoint") + curlCmd := exec.Command(c.client, "exec", c.curlPodName, "--namespace", c.namespace, "--", + "curl", "-v", "-k", "-H", "Authorization: Bearer "+token, c.metricsURL) + output, err := curlCmd.CombinedOutput() + require.NoError(t, err, "Error calling metrics endpoint: %s", string(output)) + require.Contains(t, string(output), "200 OK", "Metrics endpoint did not return 200 OK") +} + +// cleanup removes the created resources. Uses a context with timeout to prevent hangs. +func (c *MetricsTestConfig) cleanup(t *testing.T) { + type objDesc struct { + resourceName string + name string + namespace string + } + objects := []objDesc{ + {"clusterrolebinding", c.clusterBinding, ""}, + {"pod", c.curlPodName, c.namespace}, + {"serviceaccount", c.serviceAccount, c.namespace}, + {"namespace", c.namespace, ""}, + } + + t.Log("Cleaning up resources") + for _, obj := range objects { + args := []string{"delete", obj.resourceName, obj.name, "--ignore-not-found=true", "--force"} + if obj.namespace != "" { + args = append(args, "--namespace", obj.namespace) + } + output, err := exec.Command(c.client, args...).CombinedOutput() + require.NoError(t, err, "Error deleting %q %q in namespace %q: %v", obj.resourceName, obj.name, obj.namespace, string(output)) + } + + // Create a context with a 60-second timeout. + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + for _, obj := range objects { + err := waitForDeletion(ctx, c.client, obj.resourceName, obj.name, obj.namespace) + require.NoError(t, err, "Error deleting %q %q in namespace %q", obj.resourceName, obj.name, obj.namespace) + t.Logf("Successfully deleted %q %q in namespace %q", obj.resourceName, obj.name, obj.namespace) + } +} + +// waitForDeletion uses "kubectl wait" to block until the specified resource is deleted +// or until the 60-second timeout is reached. +func waitForDeletion(ctx context.Context, client, resourceType, resourceName, resourceNamespace string) error { + args := []string{"wait", "--for=delete", "--timeout=60s", resourceType, resourceName} + if resourceNamespace != "" { + args = append(args, "--namespace", resourceNamespace) + } + cmd := exec.CommandContext(ctx, client, args...) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error waiting for deletion of %s %s: %v, output: %s", resourceType, resourceName, err, string(output)) + } + return nil +} + +// createRandomNamespace creates a random namespace +func createRandomNamespace(t *testing.T, client string) string { + nsName := fmt.Sprintf("testns-%s", rand.String(8)) + + cmd := exec.Command(client, "create", "namespace", nsName) + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Error creating namespace: %s", string(output)) + + return nsName +} + +// getComponentNamespace returns the namespace where operator-controller or catalogd is running +func getComponentNamespace(t *testing.T, client, selector string) string { + cmd := exec.Command(client, "get", "pods", "--all-namespaces", "--selector="+selector, "--output=jsonpath={.items[0].metadata.namespace}") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Error determining namespace: %s", string(output)) + + namespace := string(bytes.TrimSpace(output)) + if namespace == "" { + t.Fatal("No namespace found for selector " + selector) + } + return namespace +} + +func stdoutAndCombined(cmd *exec.Cmd) ([]byte, []byte, error) { + var outOnly, outAndErr bytes.Buffer + allWriter := io.MultiWriter(&outOnly, &outAndErr) + cmd.Stdout = allWriter + cmd.Stderr = &outAndErr + err := cmd.Run() + return outOnly.Bytes(), outAndErr.Bytes(), err +} diff --git a/test/e2e/network_policy_test.go b/test/e2e/network_policy_test.go new file mode 100644 index 0000000000..00143df416 --- /dev/null +++ b/test/e2e/network_policy_test.go @@ -0,0 +1,379 @@ +package e2e + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" +) + +const ( + minJustificationLength = 40 + catalogdManagerSelector = "control-plane=catalogd-controller-manager" + operatorManagerSelector = "control-plane=operator-controller-controller-manager" + catalogdMetricsPort = 7443 + catalogdWebhookPort = 9443 + catalogServerPort = 8443 + operatorControllerMetricsPort = 8443 +) + +type portWithJustification struct { + port []networkingv1.NetworkPolicyPort + justification string +} + +// ingressRule defines a k8s IngressRule, along with a justification. +type ingressRule struct { + ports []portWithJustification + from []networkingv1.NetworkPolicyPeer +} + +// egressRule defines a k8s egressRule, along with a justification. +type egressRule struct { + ports []portWithJustification + to []networkingv1.NetworkPolicyPeer +} + +// AllowedPolicyDefinition defines the expected structure and justifications for a NetworkPolicy. +type allowedPolicyDefinition struct { + selector metav1.LabelSelector + policyTypes []networkingv1.PolicyType + ingressRule ingressRule + egressRule egressRule + denyAllIngressJustification string // Justification if Ingress is in PolicyTypes and IngressRules is empty + denyAllEgressJustification string // Justification if Egress is in PolicyTypes and EgressRules is empty +} + +var denyAllPolicySpec = allowedPolicyDefinition{ + selector: metav1.LabelSelector{}, // Empty selector, matches all pods + policyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress}, + // No IngressRules means deny all ingress if PolicyTypeIngress is present + // No EgressRules means deny all egress if PolicyTypeEgress is present + denyAllIngressJustification: "Denies all ingress traffic to pods selected by this policy by default, unless explicitly allowed by other policy rules, ensuring a baseline secure posture.", + denyAllEgressJustification: "Denies all egress traffic from pods selected by this policy by default, unless explicitly allowed by other policy rules, minimizing potential exfiltration paths.", +} + +var prometheuSpec = allowedPolicyDefinition{ + selector: metav1.LabelSelector{MatchLabels: map[string]string{"app.kubernetes.io/name": "prometheus"}}, + policyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress}, + ingressRule: ingressRule{ + ports: []portWithJustification{ + { + port: nil, + justification: "Allows access to the prometheus pod", + }, + }, + }, + egressRule: egressRule{ + ports: []portWithJustification{ + { + port: nil, + justification: "Allows prometheus to access other pods", + }, + }, + }, +} + +// Ref: https://docs.google.com/document/d/1bHEEWzA65u-kjJFQRUY1iBuMIIM1HbPy4MeDLX4NI3o/edit?usp=sharing +var allowedNetworkPolicies = map[string]allowedPolicyDefinition{ + "catalogd-controller-manager": { + selector: metav1.LabelSelector{MatchLabels: map[string]string{"control-plane": "catalogd-controller-manager"}}, + policyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress}, + ingressRule: ingressRule{ + ports: []portWithJustification{ + { + port: []networkingv1.NetworkPolicyPort{{Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: catalogdMetricsPort}}}, + justification: "Allows Prometheus to scrape metrics from catalogd, which is essential for monitoring its performance and health.", + }, + { + port: []networkingv1.NetworkPolicyPort{{Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: catalogdWebhookPort}}}, + justification: "Permits Kubernetes API server to reach catalogd's mutating admission webhook, ensuring integrity of catalog resources.", + }, + { + port: []networkingv1.NetworkPolicyPort{{Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: catalogServerPort}}}, + justification: "Enables clients (eg. operator-controller) to query catalog metadata from catalogd, which is a core function for bundle resolution and operator discovery.", + }, + }, + }, + egressRule: egressRule{ + ports: []portWithJustification{ + { + port: nil, // Empty Ports means allow all egress + justification: "Permits catalogd to fetch catalog images from arbitrary container registries and communicate with the Kubernetes API server for its operational needs.", + }, + }, + }, + }, + "operator-controller-controller-manager": { + selector: metav1.LabelSelector{MatchLabels: map[string]string{"control-plane": "operator-controller-controller-manager"}}, + policyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress}, + ingressRule: ingressRule{ + ports: []portWithJustification{ + { + port: []networkingv1.NetworkPolicyPort{{Protocol: ptr.To(corev1.ProtocolTCP), Port: &intstr.IntOrString{Type: intstr.Int, IntVal: operatorControllerMetricsPort}}}, + justification: "Allows Prometheus to scrape metrics from operator-controller, which is crucial for monitoring its activity, reconciliations, and overall health.", + }, + }, + }, + egressRule: egressRule{ + ports: []portWithJustification{ + { + port: nil, // Empty Ports means allow all egress + justification: "Enables operator-controller to pull bundle images from arbitrary image registries, connect to catalogd's HTTPS server for metadata, and interact with the Kubernetes API server.", + }, + }, + }, + }, +} + +func TestNetworkPolicyJustifications(t *testing.T) { + ctx := context.Background() + + // Validate justifications have min length in the allowedNetworkPolicies definition + for name, policyDef := range allowedNetworkPolicies { + for i, pwj := range policyDef.ingressRule.ports { + require.GreaterOrEqualf(t, len(pwj.justification), minJustificationLength, + "Justification for ingress PortWithJustification entry %d in policy %q is too short: %q", i, name, pwj.justification) + } + for i, pwj := range policyDef.egressRule.ports { // Corrected variable name from 'rule' to 'pwj' + require.GreaterOrEqualf(t, len(pwj.justification), minJustificationLength, + "Justification for egress PortWithJustification entry %d in policy %q is too short: %q", i, name, pwj.justification) + } + if policyDef.denyAllIngressJustification != "" { + require.GreaterOrEqualf(t, len(policyDef.denyAllIngressJustification), minJustificationLength, + "DenyAllIngressJustification for policy %q is too short: %q", name, policyDef.denyAllIngressJustification) + } + if policyDef.denyAllEgressJustification != "" { + require.GreaterOrEqualf(t, len(policyDef.denyAllEgressJustification), minJustificationLength, + "DenyAllEgressJustification for policy %q is too short: %q", name, policyDef.denyAllEgressJustification) + } + } + + clientForComponent := utils.FindK8sClient(t) + + operatorControllerNamespace := getComponentNamespace(t, clientForComponent, operatorManagerSelector) + catalogDNamespace := getComponentNamespace(t, clientForComponent, catalogdManagerSelector) + + policies := &networkingv1.NetworkPolicyList{} + err := c.List(ctx, policies, client.InNamespace(operatorControllerNamespace)) + require.NoError(t, err, "Failed to list NetworkPolicies in namespace %q", operatorControllerNamespace) + + clusterPolicies := policies.Items + + if operatorControllerNamespace != catalogDNamespace { + policies := &networkingv1.NetworkPolicyList{} + err := c.List(ctx, policies, client.InNamespace(catalogDNamespace)) + require.NoError(t, err, "Failed to list NetworkPolicies in namespace %q", catalogDNamespace) + clusterPolicies = append(clusterPolicies, policies.Items...) + + t.Log("Detected dual-namespace configuration, expecting two prefixed 'default-deny-all-traffic' policies.") + allowedNetworkPolicies["catalogd-default-deny-all-traffic"] = denyAllPolicySpec + allowedNetworkPolicies["operator-controller-default-deny-all-traffic"] = denyAllPolicySpec + } else { + t.Log("Detected single-namespace configuration, expecting one 'default-deny-all-traffic' policy.") + allowedNetworkPolicies["default-deny-all-traffic"] = denyAllPolicySpec + t.Log("Detected single-namespace configuration, expecting 'prometheus' policy.") + allowedNetworkPolicies["prometheus"] = prometheuSpec + } + + validatedRegistryPolicies := make(map[string]bool) + + for _, policy := range clusterPolicies { + t.Run(fmt.Sprintf("Policy_%s", strings.ReplaceAll(policy.Name, "-", "_")), func(t *testing.T) { + expectedPolicy, found := allowedNetworkPolicies[policy.Name] + require.Truef(t, found, "NetworkPolicy %q found in cluster but not in allowed registry. Namespace: %s", policy.Name, policy.Namespace) + validatedRegistryPolicies[policy.Name] = true + + // 1. Compare PodSelector + require.True(t, equality.Semantic.DeepEqual(expectedPolicy.selector, policy.Spec.PodSelector), + "PodSelector mismatch for policy %q. Expected: %+v, Got: %+v", policy.Name, expectedPolicy.selector, policy.Spec.PodSelector) + + // 2. Compare PolicyTypes + require.ElementsMatchf(t, expectedPolicy.policyTypes, policy.Spec.PolicyTypes, + "PolicyTypes mismatch for policy %q.", policy.Name) + + // 3. Validate Ingress Rules + hasIngressPolicyType := false + for _, pt := range policy.Spec.PolicyTypes { + if pt == networkingv1.PolicyTypeIngress { + hasIngressPolicyType = true + break + } + } + + if hasIngressPolicyType { + switch len(policy.Spec.Ingress) { + case 0: + validateDenyAllIngress(t, policy.Name, expectedPolicy) + case 1: + validateSingleIngressRule(t, policy.Name, policy.Spec.Ingress[0], expectedPolicy) + default: + require.Failf(t, "Policy %q in cluster has %d ingress rules. Allowed definition supports at most 1 explicit ingress rule.", policy.Name, len(policy.Spec.Ingress)) + } + } else { + validateNoIngress(t, policy.Name, policy, expectedPolicy) + } + + // 4. Validate Egress Rules + hasEgressPolicyType := false + for _, pt := range policy.Spec.PolicyTypes { + if pt == networkingv1.PolicyTypeEgress { + hasEgressPolicyType = true + break + } + } + + if hasEgressPolicyType { + switch len(policy.Spec.Egress) { + case 0: + validateDenyAllEgress(t, policy.Name, expectedPolicy) + case 1: + validateSingleEgressRule(t, policy.Name, policy.Spec.Egress[0], expectedPolicy) + default: + require.Failf(t, "Policy %q in cluster has %d egress rules. Allowed definition supports at most 1 explicit egress rule.", policy.Name, len(policy.Spec.Egress)) + } + } else { + validateNoEgress(t, policy, expectedPolicy) + } + }) + } + + // 5. Ensure all policies in the registry were found in the cluster + require.Len(t, validatedRegistryPolicies, len(allowedNetworkPolicies), + "Mismatch between number of expected policies in registry (%d) and number of policies found & validated in cluster (%d). Missing policies from registry: %v", len(allowedNetworkPolicies), len(validatedRegistryPolicies), missingPolicies(allowedNetworkPolicies, validatedRegistryPolicies)) +} + +func missingPolicies(expected map[string]allowedPolicyDefinition, actual map[string]bool) []string { + missing := []string{} + for k := range expected { + if !actual[k] { + missing = append(missing, k) + } + } + return missing +} + +// validateNoEgress confirms that a policy which does not have spec.PolicyType=Egress specified +// has no corresponding egress rules or expectations defined. +func validateNoEgress(t *testing.T, policy networkingv1.NetworkPolicy, expectedPolicy allowedPolicyDefinition) { + // Policy is NOT expected to affect Egress traffic (no Egress in PolicyTypes) + // Expected: Cluster has no egress rules; Registry has no DenyAllEgressJustification and empty EgressRule. + require.Emptyf(t, policy.Spec.Egress, + "Policy %q: Cluster does not have Egress PolicyType, but has Egress rules defined.", policy.Name) + require.Emptyf(t, expectedPolicy.denyAllEgressJustification, + "Policy %q: Cluster does not have Egress PolicyType. Registry's DenyAllEgressJustification is not empty.", policy.Name) + require.Emptyf(t, expectedPolicy.egressRule.ports, + "Policy %q: Cluster does not have Egress PolicyType. Registry's EgressRule.Ports is not empty.", policy.Name) + require.Emptyf(t, expectedPolicy.egressRule.to, + "Policy %q: Cluster does not have Egress PolicyType. Registry's EgressRule.To is not empty.", policy.Name) +} + +// validateDenyAllEgress confirms that a policy with Egress PolicyType but no explicit rules +// correctly corresponds to a "deny all" expectation. +func validateDenyAllEgress(t *testing.T, policyName string, expectedPolicy allowedPolicyDefinition) { + // Cluster: PolicyType Egress is present, but no explicit egress rules -> Deny All Egress by this policy. + // Expected: DenyAllEgressJustification is set; EgressRule.Ports and .To are empty. + require.NotEmptyf(t, expectedPolicy.denyAllEgressJustification, + "Policy %q: Cluster has Egress PolicyType but no rules (deny all). Registry's DenyAllEgressJustification is empty.", policyName) + require.Emptyf(t, expectedPolicy.egressRule.ports, + "Policy %q: Cluster has Egress PolicyType but no rules (deny all). Registry's EgressRule.Ports is not empty.", policyName) + require.Emptyf(t, expectedPolicy.egressRule.to, + "Policy %q: Cluster has Egress PolicyType but no rules (deny all). Registry's EgressRule.To is not empty.", policyName) +} + +// validateSingleEgressRule validates a policy that has exactly one explicit egress rule, +// distinguishing between "allow-all" and more specific rules. +func validateSingleEgressRule(t *testing.T, policyName string, clusterEgressRule networkingv1.NetworkPolicyEgressRule, expectedPolicy allowedPolicyDefinition) { + // Cluster: PolicyType Egress is present, and there's one explicit egress rule. + // Expected: DenyAllEgressJustification is empty; EgressRule matches the cluster's rule. + expectedEgressRule := expectedPolicy.egressRule + + require.Emptyf(t, expectedPolicy.denyAllEgressJustification, + "Policy %q: Cluster has a specific Egress rule. Registry's DenyAllEgressJustification should be empty.", policyName) + + isClusterRuleAllowAllPorts := len(clusterEgressRule.Ports) == 0 + isClusterRuleAllowAllPeers := len(clusterEgressRule.To) == 0 + + if isClusterRuleAllowAllPorts && isClusterRuleAllowAllPeers { // Handles egress: [{}] - allow all ports to all peers + require.Lenf(t, expectedEgressRule.ports, 1, + "Policy %q (allow-all egress): Expected EgressRule.Ports to have 1 justification entry, got %d", policyName, len(expectedEgressRule.ports)) + if len(expectedEgressRule.ports) == 1 { // Guard against panic + require.Nilf(t, expectedEgressRule.ports[0].port, + "Policy %q (allow-all egress): Expected EgressRule.Ports[0].Port to be nil, got %+v", policyName, expectedEgressRule.ports[0].port) + } + require.Conditionf(t, func() bool { return len(expectedEgressRule.to) == 0 }, + "Policy %q (allow-all egress): Expected EgressRule.To to be empty for allow-all peers, got %+v", policyName, expectedEgressRule.to) + } else { + // Specific egress rule (not the simple allow-all ports and allow-all peers) + require.True(t, equality.Semantic.DeepEqual(expectedEgressRule.to, clusterEgressRule.To), + "Policy %q, Egress Rule: 'To' mismatch.\nExpected: %+v\nGot: %+v", policyName, expectedEgressRule.to, clusterEgressRule.To) + + var allExpectedPortsFromPwJ []networkingv1.NetworkPolicyPort + for _, pwj := range expectedEgressRule.ports { + allExpectedPortsFromPwJ = append(allExpectedPortsFromPwJ, pwj.port...) + } + require.ElementsMatchf(t, allExpectedPortsFromPwJ, clusterEgressRule.Ports, + "Policy %q, Egress Rule: 'Ports' mismatch (aggregated from PortWithJustification). Expected: %+v, Got: %+v", policyName, allExpectedPortsFromPwJ, clusterEgressRule.Ports) + } +} + +// validateNoIngress confirms that a policy which does not have the Ingress PolicyType +// has no corresponding ingress rules or expectations defined. +func validateNoIngress(t *testing.T, policyName string, clusterPolicy networkingv1.NetworkPolicy, expectedPolicy allowedPolicyDefinition) { + // Policy is NOT expected to affect Ingress traffic (no Ingress in PolicyTypes) + // Expected: Cluster has no ingress rules; Registry has no DenyAllIngressJustification and empty IngressRule. + require.Emptyf(t, clusterPolicy.Spec.Ingress, + "Policy %q: Cluster does not have Ingress PolicyType, but has Ingress rules defined.", policyName) + require.Emptyf(t, expectedPolicy.denyAllIngressJustification, + "Policy %q: Cluster does not have Ingress PolicyType. Registry's DenyAllIngressJustification is not empty.", policyName) + require.Emptyf(t, expectedPolicy.ingressRule.ports, + "Policy %q: Cluster does not have Ingress PolicyType. Registry's IngressRule.Ports is not empty.", policyName) + require.Emptyf(t, expectedPolicy.ingressRule.from, + "Policy %q: Cluster does not have Ingress PolicyType. Registry's IngressRule.From is not empty.", policyName) +} + +// validateDenyAllIngress confirms that a policy with Ingress PolicyType but no explicit rules +// correctly corresponds to a "deny all" expectation. +func validateDenyAllIngress(t *testing.T, policyName string, expectedPolicy allowedPolicyDefinition) { + // Cluster: PolicyType Ingress is present, but no explicit ingress rules -> Deny All Ingress by this policy. + // Expected: DenyAllIngressJustification is set; IngressRule.Ports and .From are empty. + require.NotEmptyf(t, expectedPolicy.denyAllIngressJustification, + "Policy %q: Cluster has Ingress PolicyType but no rules (deny all). Registry's DenyAllIngressJustification is empty.", policyName) + require.Emptyf(t, expectedPolicy.ingressRule.ports, + "Policy %q: Cluster has Ingress PolicyType but no rules (deny all). Registry's IngressRule.Ports is not empty.", policyName) + require.Emptyf(t, expectedPolicy.ingressRule.from, + "Policy %q: Cluster has Ingress PolicyType but no rules (deny all). Registry's IngressRule.From is not empty.", policyName) +} + +// validateSingleIngressRule validates a policy that has exactly one explicit ingress rule. +func validateSingleIngressRule(t *testing.T, policyName string, clusterIngressRule networkingv1.NetworkPolicyIngressRule, expectedPolicy allowedPolicyDefinition) { + // Cluster: PolicyType Ingress is present, and there's one explicit ingress rule. + // Expected: DenyAllIngressJustification is empty; IngressRule matches the cluster's rule. + expectedIngressRule := expectedPolicy.ingressRule + + require.Emptyf(t, expectedPolicy.denyAllIngressJustification, + "Policy %q: Cluster has a specific Ingress rule. Registry's DenyAllIngressJustification should be empty.", policyName) + + // Compare 'From' + require.True(t, equality.Semantic.DeepEqual(expectedIngressRule.from, clusterIngressRule.From), + "Policy %q, Ingress Rule: 'From' mismatch.\nExpected: %+v\nGot: %+v", policyName, expectedIngressRule.from, clusterIngressRule.From) + + // Compare 'Ports' by aggregating the ports from our justified structure + var allExpectedPortsFromPwJ []networkingv1.NetworkPolicyPort + for _, pwj := range expectedIngressRule.ports { + allExpectedPortsFromPwJ = append(allExpectedPortsFromPwJ, pwj.port...) + } + require.ElementsMatchf(t, allExpectedPortsFromPwJ, clusterIngressRule.Ports, + "Policy %q, Ingress Rule: 'Ports' mismatch (aggregated from PortWithJustification). Expected: %+v, Got: %+v", policyName, allExpectedPortsFromPwJ, clusterIngressRule.Ports) +} diff --git a/test/e2e/single_namespace_support_test.go b/test/e2e/single_namespace_support_test.go new file mode 100644 index 0000000000..7e8fe7bd5b --- /dev/null +++ b/test/e2e/single_namespace_support_test.go @@ -0,0 +1,410 @@ +package e2e + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" + . "github.com/operator-framework/operator-controller/test/helpers" +) + +const ( + soNsFlag = "SingleOwnNamespaceInstallSupport" +) + +func TestClusterExtensionSingleNamespaceSupport(t *testing.T) { + SkipIfFeatureGateDisabled(t, soNsFlag) + t.Log("Test support for cluster extension config") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + t.Log("By creating install namespace, watch namespace and necessary rbac resources") + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "single-namespace-operator", + }, + } + require.NoError(t, c.Create(t.Context(), &namespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &namespace)) + }) + + watchNamespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "single-namespace-operator-target", + }, + } + require.NoError(t, c.Create(t.Context(), &watchNamespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &watchNamespace)) + }) + + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "single-namespace-operator-installer", + Namespace: namespace.GetName(), + }, + } + require.NoError(t, c.Create(t.Context(), &serviceAccount)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &serviceAccount)) + }) + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "single-namespace-operator-installer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: corev1.GroupName, + Name: serviceAccount.GetName(), + Namespace: serviceAccount.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) + }) + + t.Log("By creating the test-catalog ClusterCatalog") + extensionCatalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), extensionCatalog)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), extensionCatalog)) + }) + + t.Log("By waiting for the catalog to serve its metadata") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By attempting to install the single-namespace-operator ClusterExtension without any configuration") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "single-namespace-operator-extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "single-namespace-operator", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: namespace.GetName(), + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount.GetName(), + }, + }, + } + require.NoError(t, c.Create(t.Context(), clusterExtension)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + }) + + t.Log("By waiting for single-namespace-operator extension installation to fail") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + require.Contains(ct, cond.Message, "required field \"watchNamespace\" is missing") + }, pollDuration, pollInterval) + + t.Log("By updating the ClusterExtension configuration with a watchNamespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(t, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.GetName()}, clusterExtension)) + clusterExtension.Spec.Config = &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, watchNamespace.GetName())), + }, + } + require.NoError(t, c.Update(t.Context(), clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By waiting for single-namespace-operator extension to be installed successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By ensuring the single-namespace-operator deployment is correctly configured to watch the watch namespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + deployment := &appsv1.Deployment{} + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "single-namespace-operator"}, deployment)) + require.NotNil(ct, deployment.Spec.Template.GetAnnotations()) + require.Equal(ct, watchNamespace.GetName(), deployment.Spec.Template.GetAnnotations()["olm.targetNamespaces"]) + }, pollDuration, pollInterval) +} + +func TestClusterExtensionOwnNamespaceSupport(t *testing.T) { + SkipIfFeatureGateDisabled(t, soNsFlag) + t.Log("Test support for cluster extension with OwnNamespace install mode support") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + t.Log("By creating install namespace, watch namespace and necessary rbac resources") + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "own-namespace-operator", + }, + } + require.NoError(t, c.Create(t.Context(), &namespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &namespace)) + }) + + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "own-namespace-operator-installer", + Namespace: namespace.GetName(), + }, + } + require.NoError(t, c.Create(t.Context(), &serviceAccount)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &serviceAccount)) + }) + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "own-namespace-operator-installer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: corev1.GroupName, + Name: serviceAccount.GetName(), + Namespace: serviceAccount.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) + }) + + t.Log("By creating the test-catalog ClusterCatalog") + extensionCatalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), extensionCatalog)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), extensionCatalog)) + }) + + t.Log("By waiting for the catalog to serve its metadata") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By attempting to install the own-namespace-operator ClusterExtension without any configuration") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "own-namespace-operator-extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "own-namespace-operator", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: namespace.GetName(), + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount.GetName(), + }, + }, + } + require.NoError(t, c.Create(t.Context(), clusterExtension)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + }) + + t.Log("By waiting for own-namespace-operator extension installation to fail") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + require.Contains(ct, cond.Message, "required field \"watchNamespace\" is missing") + }, pollDuration, pollInterval) + + t.Log("By updating the ClusterExtension configuration with a watchNamespace other than the install namespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(t, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.GetName()}, clusterExtension)) + clusterExtension.Spec.Config = &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(`{"watchNamespace": "some-namespace"}`), + }, + } + require.NoError(t, c.Update(t.Context(), clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By waiting for own-namespace-operator extension installation to fail") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonRetrying, cond.Reason) + require.Contains(ct, cond.Message, fmt.Sprintf("invalid 'watchNamespace' \"some-namespace\": must be install namespace (%s)", clusterExtension.Spec.Namespace)) + }, pollDuration, pollInterval) + + t.Log("By updating the ClusterExtension configuration with a watchNamespace = install namespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(t, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.GetName()}, clusterExtension)) + clusterExtension.Spec.Config = &ocv1.ClusterExtensionConfig{ + ConfigType: ocv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, clusterExtension.Spec.Namespace)), + }, + } + require.NoError(t, c.Update(t.Context(), clusterExtension)) + }, pollDuration, pollInterval) + + t.Log("By waiting for own-namespace-operator extension to be installed successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By ensuring the own-namespace-operator deployment is correctly configured to watch the watch namespace") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + deployment := &appsv1.Deployment{} + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "own-namespace-operator"}, deployment)) + require.NotNil(ct, deployment.Spec.Template.GetAnnotations()) + require.Equal(ct, clusterExtension.Spec.Namespace, deployment.Spec.Template.GetAnnotations()["olm.targetNamespaces"]) + }, pollDuration, pollInterval) +} + +func TestClusterExtensionVersionUpdate(t *testing.T) { + SkipIfFeatureGateDisabled(t, soNsFlag) + t.Log("When a cluster extension is installed from a catalog") + t.Log("When resolving upgrade edges") + + clusterExtension, extensionCatalog, sa, ns := TestInit(t) + defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns) + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + t.Log("By creating an ClusterExtension at a specified version") + clusterExtension.Spec = ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "test", + Version: "1.0.0", + }, + }, + Namespace: ns.Name, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, + }, + } + require.NoError(t, c.Create(context.Background(), clusterExtension)) + t.Log("By eventually reporting a successful resolution") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("It allows to upgrade the ClusterExtension to a non-successor version") + t.Log("By forcing update of ClusterExtension resource to a non-successor version") + // 1.2.0 does not replace/skip/skipRange 1.0.0. + clusterExtension.Spec.Source.Catalog.Version = "1.2.0" + clusterExtension.Spec.Source.Catalog.UpgradeConstraintPolicy = ocv1.UpgradeConstraintPolicySelfCertified + require.NoError(t, c.Update(context.Background(), clusterExtension)) + t.Log("By eventually reporting a satisfiable resolution") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + t.Log("We should have two ClusterExtensionRevision resources") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + cerList := &ocv1.ClusterExtensionRevisionList{} + require.NoError(ct, c.List(context.Background(), cerList)) + require.Len(ct, cerList.Items, 2) + }, pollDuration, pollInterval) +} diff --git a/test/e2e/webhook_support_test.go b/test/e2e/webhook_support_test.go new file mode 100644 index 0000000000..1c80c615be --- /dev/null +++ b/test/e2e/webhook_support_test.go @@ -0,0 +1,237 @@ +package e2e + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/utils/ptr" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" + . "github.com/operator-framework/operator-controller/test/helpers" +) + +var dynamicClient dynamic.Interface + +func TestWebhookSupport(t *testing.T) { + SkipIfFeatureGateDisabled(t, "WebhookProviderCertManager") + t.Log("Test support for bundles with webhooks") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + if dynamicClient == nil { + var err error + dynamicClient, err = dynamic.NewForConfig(cfg) + require.NoError(t, err) + } + + t.Log("By creating install namespace, and necessary rbac resources") + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator", + }, + } + require.NoError(t, c.Create(t.Context(), &namespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &namespace)) + }) + + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-installer", + Namespace: namespace.GetName(), + }, + } + require.NoError(t, c.Create(t.Context(), &serviceAccount)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &serviceAccount)) + }) + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-installer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: corev1.GroupName, + Name: serviceAccount.GetName(), + Namespace: serviceAccount.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) + }) + + t.Log("By creating the webhook-operator ClusterCatalog") + extensionCatalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), extensionCatalog)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), extensionCatalog)) + }) + + t.Log("By waiting for the catalog to serve its metadata") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By installing the webhook-operator ClusterExtension") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "webhook-operator", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: namespace.GetName(), + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount.GetName(), + }, + }, + } + require.NoError(t, c.Create(t.Context(), clusterExtension)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + }) + + t.Log("By waiting for webhook-operator extension to be installed successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By waiting for webhook-operator deployment to be available") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + deployment := &appsv1.Deployment{} + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "webhook-operator-controller-manager"}, deployment)) + available := false + for _, cond := range deployment.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + available = cond.Status == corev1.ConditionTrue + } + } + require.True(ct, available) + }, pollDuration, pollInterval) + + v1Gvr := schema.GroupVersionResource{ + Group: "webhook.operators.coreos.io", + Version: "v1", + Resource: "webhooktests", + } + v1Client := dynamicClient.Resource(v1Gvr).Namespace(namespace.GetName()) + + t.Log("By eventually seeing that invalid CR creation is rejected by the validating webhook") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + obj := getWebhookOperatorResource("invalid-test-cr", namespace.GetName(), false) + _, err := v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) + require.Error(ct, err) + require.Contains(ct, err.Error(), "Invalid value: false: Spec.Valid must be true") + }, pollDuration, pollInterval) + + var ( + res *unstructured.Unstructured + err error + obj = getWebhookOperatorResource("valid-test-cr", namespace.GetName(), true) + ) + + t.Log("By eventually creating a valid CR") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + res, err = v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) + require.NoError(ct, err) + }, pollDuration, pollInterval) + t.Cleanup(func() { + require.NoError(t, v1Client.Delete(context.Background(), obj.GetName(), metav1.DeleteOptions{})) + }) + + require.Equal(t, map[string]interface{}{ + "valid": true, + "mutate": true, + }, res.Object["spec"]) + + t.Log("By checking a valid CR is converted to v2 by the conversion webhook") + v2Gvr := schema.GroupVersionResource{ + Group: "webhook.operators.coreos.io", + Version: "v2", + Resource: "webhooktests", + } + v2Client := dynamicClient.Resource(v2Gvr).Namespace(namespace.GetName()) + + t.Log("By eventually getting the valid CR with a v2 client") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + res, err = v2Client.Get(t.Context(), obj.GetName(), metav1.GetOptions{}) + require.NoError(ct, err) + }, pollDuration, pollInterval) + + t.Log("and verifying that the CR is correctly converted") + require.Equal(t, map[string]interface{}{ + "conversion": map[string]interface{}{ + "valid": true, + "mutate": true, + }, + }, res.Object["spec"]) +} + +func getWebhookOperatorResource(name string, namespace string, valid bool) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "webhooktests", + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "valid": valid, + }, + }, + } +} diff --git a/test/extension-developer-e2e/extension_developer_test.go b/test/extension-developer-e2e/extension_developer_test.go index 91e7391a9c..a71f1f83d6 100644 --- a/test/extension-developer-e2e/extension_developer_test.go +++ b/test/extension-developer-e2e/extension_developer_test.go @@ -18,9 +18,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" ) func TestExtensionDeveloper(t *testing.T) { @@ -29,26 +27,28 @@ func TestExtensionDeveloper(t *testing.T) { scheme := runtime.NewScheme() - require.NoError(t, catalogd.AddToScheme(scheme)) - require.NoError(t, ocv1alpha1.AddToScheme(scheme)) + require.NoError(t, ocv1.AddToScheme(scheme)) + require.NoError(t, ocv1.AddToScheme(scheme)) require.NoError(t, corev1.AddToScheme(scheme)) require.NoError(t, rbacv1.AddToScheme(scheme)) + require.NotEmpty(t, os.Getenv("CATALOG_IMG"), "environment variable CATALOG_IMG must be set") + require.NotEmpty(t, os.Getenv("REG_PKG_NAME"), "environment variable REG_PKG_NAME must be set") + c, err := client.New(cfg, client.Options{Scheme: scheme}) require.NoError(t, err) ctx := context.Background() - catalog := &catalogd.ClusterCatalog{ + catalog := &ocv1.ClusterCatalog{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "catalog", }, - Spec: catalogd.ClusterCatalogSpec{ - Source: catalogd.CatalogSource{ - Type: catalogd.SourceTypeImage, - Image: &catalogd.ImageSource{ - Ref: os.Getenv("CATALOG_IMG"), - InsecureSkipTLSVerify: true, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: os.Getenv("CATALOG_IMG"), }, }, }, @@ -65,22 +65,20 @@ func TestExtensionDeveloper(t *testing.T) { } require.NoError(t, c.Create(ctx, sa)) - clusterExtension := &ocv1alpha1.ClusterExtension{ + clusterExtension := &ocv1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: "registryv1", }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - Source: ocv1alpha1.SourceConfig{ + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ + Catalog: &ocv1.CatalogFilter{ PackageName: os.Getenv("REG_PKG_NAME"), }, }, - Install: ocv1alpha1.ClusterExtensionInstallConfig{ - Namespace: installNamespace, - ServiceAccount: ocv1alpha1.ServiceAccountReference{ - Name: sa.Name, - }, + Namespace: installNamespace, + ServiceAccount: ocv1.ServiceAccountReference{ + Name: sa.Name, }, }, } @@ -107,6 +105,7 @@ func TestExtensionDeveloper(t *testing.T) { "", }, Resources: []string{ + "configmaps", "services", "serviceaccounts", }, @@ -199,18 +198,16 @@ func TestExtensionDeveloper(t *testing.T) { } require.NoError(t, c.Create(ctx, crb)) - t.Logf("When creating an ClusterExtension that references a package with a %q bundle type", clusterExtension.ObjectMeta.Name) + t.Logf("When creating an ClusterExtension that references a package with a %q bundle type", clusterExtension.Name) require.NoError(t, c.Create(context.Background(), clusterExtension)) t.Log("It should have a status condition type of Installed with a status of True and a reason of Success") require.EventuallyWithT(t, func(ct *assert.CollectT) { - ext := &ocv1alpha1.ClusterExtension{} - assert.NoError(ct, c.Get(context.Background(), client.ObjectKeyFromObject(clusterExtension), ext)) - cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) + ext := &ocv1.ClusterExtension{} + require.NoError(ct, c.Get(context.Background(), client.ObjectKeyFromObject(clusterExtension), ext)) + cond := meta.FindStatusCondition(ext.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, 2*time.Minute, time.Second) require.NoError(t, c.Delete(context.Background(), catalog)) require.NoError(t, c.Delete(context.Background(), clusterExtension)) diff --git a/test/extension-developer-e2e/setup.sh b/test/extension-developer-e2e/setup.sh index 889080ad6c..293341b33b 100755 --- a/test/extension-developer-e2e/setup.sh +++ b/test/extension-developer-e2e/setup.sh @@ -11,27 +11,26 @@ following bundle formats: This script will ensure that all images built are loaded onto a KinD cluster with the name specified in the arguments. The following environment variables are required for configuring this script: -- \$CATALOG_IMG - the tag for the catalog image that contains the registry+v1 bundle. +- \$E2E_TEST_CATALOG_V1 - the tag for the catalog image that contains the registry+v1 bundle. - \$REG_PKG_NAME - the name of the package for the extension that uses the registry+v1 bundle format. -- \$LOCAL_REGISTRY_HOST - hostname:port of the local docker-registry setup.sh also takes 5 arguments. Usage: - setup.sh [OPERATOR_SDK] [CONTAINER_RUNTIME] [KUSTOMIZE] [KIND] [KIND_CLUSTER_NAME] [NAMESPACE] + setup.sh [OPERATOR_SDK] [CONTAINER_RUNTIME] [KUSTOMIZE] [LOCAL_REGISTRY_HOST] [CLUSTER_REGISTRY_HOST] " ######################################## # Input validation ######################################## -if [[ "$#" -ne 6 ]]; then +if [[ "$#" -ne 5 ]]; then echo "Illegal number of arguments passed" echo "${help}" exit 1 fi -if [[ -z "${CATALOG_IMG}" ]]; then - echo "\$CATALOG_IMG is required to be set" +if [[ -z "${E2E_TEST_CATALOG_V1}" ]]; then + echo "\$E2E_TEST_CATALOG_V1 is required to be set" echo "${help}" exit 1 fi @@ -42,12 +41,6 @@ if [[ -z "${REG_PKG_NAME}" ]]; then exit 1 fi -if [[ -z "${LOCAL_REGISTRY_HOST}" ]]; then - echo "\$LOCAL_REGISTRY_HOST is required to be set" - echo "${help}" - exit 1 -fi - ######################################## # Setup temp dir and local variables ######################################## @@ -64,15 +57,25 @@ mkdir -p "${REG_DIR}" operator_sdk=$1 container_tool=$2 kustomize=$3 -kind=$4 -kcluster_name=$5 -namespace=$6 +# The path we use to push the image from _outside_ the cluster +local_registry_host=$4 +# The path we use _inside_ the cluster +cluster_registry_host=$5 + +tls_flag="" +if [[ "$container_tool" == "podman" ]]; then + echo "Using podman container runtime; adding tls disable flag" + tls_flag="--tls-verify=false" +fi + +catalog_push_tag="${local_registry_host}/${E2E_TEST_CATALOG_V1}" +reg_pkg_name="${REG_PKG_NAME}" reg_img="${DOMAIN}/registry:v0.0.1" -reg_bundle_img="${LOCAL_REGISTRY_HOST}/bundles/registry-v1/registry-bundle:v0.0.1" +reg_bundle_path="bundles/registry-v1/registry-bundle:v0.0.1" -catalog_img="${CATALOG_IMG}" -reg_pkg_name="${REG_PKG_NAME}" +reg_bundle_img="${cluster_registry_host}/${reg_bundle_path}" +reg_bundle_push_tag="${local_registry_host}/${reg_bundle_path}" ######################################## # Create the registry+v1 based extension @@ -84,7 +87,7 @@ reg_pkg_name="${REG_PKG_NAME}" # NOTE: This is a rough edge that users will experience # The Makefile in the project scaffolded by operator-sdk uses an SDK binary -# in the path path if it is present. Override via `export` to ensure we use +# in the path if it is present. Override via `export` to ensure we use # the same version that we scaffolded with. # NOTE: this is a rough edge that users will experience @@ -102,7 +105,8 @@ reg_pkg_name="${REG_PKG_NAME}" make docker-build IMG="${reg_img}" && \ sed -i -e 's/$(OPERATOR_SDK) generate kustomize manifests -q/$(OPERATOR_SDK) generate kustomize manifests -q --interactive=false/g' Makefile && \ make bundle IMG="${reg_img}" VERSION=0.0.1 && \ - make bundle-build BUNDLE_IMG="${reg_bundle_img}" + make bundle-build BUNDLE_IMG="${reg_bundle_push_tag}" + ${container_tool} push ${reg_bundle_push_tag} ${tls_flag} ) ############################### @@ -149,107 +153,5 @@ cat < "${TMP_ROOT}"/catalog/index.yaml } EOF -# Add a .indexignore to make catalogd ignore -# reading the symlinked ..* files that are created when -# mounting a ConfigMap -cat < "${TMP_ROOT}"/catalog/.indexignore -..* -EOF - -kubectl create configmap -n "${namespace}" --from-file="${TMP_ROOT}"/catalog.Dockerfile extension-dev-e2e.dockerfile -kubectl create configmap -n "${namespace}" --from-file="${TMP_ROOT}"/catalog extension-dev-e2e.build-contents - -kubectl apply -f - << EOF -apiVersion: batch/v1 -kind: Job -metadata: - name: kaniko - namespace: "${namespace}" -spec: - template: - spec: - containers: - - name: kaniko - image: gcr.io/kaniko-project/executor:latest - args: ["--dockerfile=/workspace/catalog.Dockerfile", - "--context=/workspace/", - "--destination=${catalog_img}", - "--skip-tls-verify"] - volumeMounts: - - name: dockerfile - mountPath: /workspace/ - - name: build-contents - mountPath: /workspace/catalog/ - restartPolicy: Never - volumes: - - name: dockerfile - configMap: - name: extension-dev-e2e.dockerfile - items: - - key: catalog.Dockerfile - path: catalog.Dockerfile - - name: build-contents - configMap: - name: extension-dev-e2e.build-contents -EOF - -kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko --timeout=60s - -# Make sure all files are removable. This is necessary because -# the Makefiles generated by the Operator-SDK have targets -# that install binaries under the bin/ directory. Those binaries -# don't have write permissions so they can't be removed unless -# we ensure they have the write permissions -chmod -R +w "${REG_DIR}/bin" - -# Load the bundle image into the docker-registry - -kubectl create configmap -n "${namespace}" --from-file="${REG_DIR}/bundle.Dockerfile" operator-controller-e2e-${reg_pkg_name}.root - -tgz="${REG_DIR}/manifests.tgz" -tar czf "${tgz}" -C "${REG_DIR}" bundle -kubectl create configmap -n "${namespace}" --from-file="${tgz}" operator-controller-${reg_pkg_name}.manifests - -kubectl apply -f - << EOF -apiVersion: batch/v1 -kind: Job -metadata: - name: "kaniko-${reg_pkg_name}" - namespace: "${namespace}" -spec: - template: - spec: - initContainers: - - name: copy-manifests - image: busybox - command: ['sh', '-c', 'cp /manifests-data/* /manifests'] - volumeMounts: - - name: manifests - mountPath: /manifests - - name: manifests-data - mountPath: /manifests-data - containers: - - name: kaniko - image: gcr.io/kaniko-project/executor:latest - args: ["--dockerfile=/workspace/bundle.Dockerfile", - "--context=tar:///workspace/manifests/manifests.tgz", - "--destination=${reg_bundle_img}", - "--skip-tls-verify"] - volumeMounts: - - name: dockerfile - mountPath: /workspace/ - - name: manifests - mountPath: /workspace/manifests/ - restartPolicy: Never - volumes: - - name: dockerfile - configMap: - name: operator-controller-e2e-${reg_pkg_name}.root - - name: manifests - emptyDir: {} - - name: manifests-data - configMap: - name: operator-controller-${reg_pkg_name}.manifests -EOF - -kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${reg_pkg_name} --timeout=60s +${container_tool} build -f "${TMP_ROOT}/catalog.Dockerfile" -t "${catalog_push_tag}" "${TMP_ROOT}/" +${container_tool} push ${catalog_push_tag} ${tls_flag} diff --git a/test/helpers/feature_gates.go b/test/helpers/feature_gates.go new file mode 100644 index 0000000000..bd8362448f --- /dev/null +++ b/test/helpers/feature_gates.go @@ -0,0 +1,108 @@ +// Package utils provides helper functions for e2e tests, including +// feature gate detection and validation utilities. +package utils + +import ( + "strings" + "sync" + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/component-base/featuregate" + "sigs.k8s.io/controller-runtime/pkg/client" + + catdfeatures "github.com/operator-framework/operator-controller/internal/catalogd/features" + opconfeatures "github.com/operator-framework/operator-controller/internal/operator-controller/features" +) + +var ( + featureGateStatus map[string]bool + featureGateStatusOnce sync.Once +) + +const ( + fgPrefix = "--feature-gates=" +) + +// SkipIfFeatureGateDisabled skips the test if the specified feature gate is disabled. +// It queries the OLM deployments to detect feature gate settings and falls back to +// programmatic defaults if the feature gate is not explicitly configured. +func SkipIfFeatureGateDisabled(t *testing.T, fg string) { + if !isFeatureGateEnabled(t, fg) { + t.Skipf("Feature-gate %q disabled", fg) + } +} + +func isFeatureGateEnabled(t *testing.T, fg string) bool { + gatherFeatureGates(t) + enabled, ok := featureGateStatus[fg] + if ok { + return enabled + } + + // Not found (i.e. not explicitly set), so we need to find the programmed default. + // Because feature-gates are organized by catd/opcon, we need to check each individually. + // To avoid a panic, we need to check if it's a known gate first. + mfgs := []featuregate.MutableFeatureGate{ + catdfeatures.CatalogdFeatureGate, + opconfeatures.OperatorControllerFeatureGate, + } + f := featuregate.Feature(fg) + for _, mfg := range mfgs { + known := mfg.GetAll() + if _, ok := known[f]; ok { + e := mfg.Enabled(f) + t.Logf("Feature-gate %q not found in arguments, defaulting to %v", fg, e) + return e + } + } + + t.Fatalf("Unknown feature-gate: %q", fg) + return false // unreachable, but required for compilation +} + +func processFeatureGate(t *testing.T, featureGateValue string) { + fgvs := strings.Split(featureGateValue, ",") + for _, fg := range fgvs { + v := strings.Split(fg, "=") + require.Len(t, v, 2, "invalid feature-gate format: %q (expected name=value)", fg) + switch v[1] { + case "true": + featureGateStatus[v[0]] = true + t.Logf("Feature-gate %q enabled", v[0]) + case "false": + featureGateStatus[v[0]] = false + t.Logf("Feature-gate %q disabled", v[0]) + default: + t.Fatalf("invalid feature-gate value: %q (expected true or false)", fg) + } + } +} + +func gatherFeatureGatesFromDeployment(t *testing.T, dep *appsv1.Deployment) { + for _, con := range dep.Spec.Template.Spec.Containers { + for _, arg := range con.Args { + if strings.HasPrefix(arg, fgPrefix) { + processFeatureGate(t, strings.TrimPrefix(arg, fgPrefix)) + } + } + } +} + +func gatherFeatureGates(t *testing.T) { + featureGateStatusOnce.Do(func() { + featureGateStatus = make(map[string]bool) + + depList := &appsv1.DeploymentList{} + err := c.List(t.Context(), depList, client.MatchingLabels{ + "app.kubernetes.io/part-of": "olm", + }) + require.NoError(t, err) + require.Len(t, depList.Items, 2) + + for _, d := range depList.Items { + gatherFeatureGatesFromDeployment(t, &d) + } + }) +} diff --git a/test/helpers/helpers.go b/test/helpers/helpers.go new file mode 100644 index 0000000000..49ebeaab6a --- /dev/null +++ b/test/helpers/helpers.go @@ -0,0 +1,383 @@ +package utils + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" +) + +var ( + cfg *rest.Config + c client.Client +) + +const ( + pollDuration = time.Minute + pollInterval = time.Second + testCatalogName = "test-catalog" + testCatalogRefEnvVar = "CATALOG_IMG" +) + +func CreateNamespace(ctx context.Context, name string) (*corev1.Namespace, error) { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + err := c.Create(ctx, ns) + if err != nil { + return nil, err + } + return ns, nil +} + +func CreateServiceAccount(ctx context.Context, name types.NamespacedName, clusterExtensionName string) (*corev1.ServiceAccount, error) { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, + } + err := c.Create(ctx, sa) + if err != nil { + return nil, err + } + + return sa, CreateClusterRoleAndBindingForSA(ctx, name.Name, sa, clusterExtensionName) +} + +func CreateClusterRoleAndBindingForSA(ctx context.Context, name string, sa *corev1.ServiceAccount, clusterExtensionName string) error { + cr := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{ + "olm.operatorframework.io", + }, + Resources: []string{ + "clusterextensions/finalizers", + }, + Verbs: []string{ + "update", + }, + ResourceNames: []string{clusterExtensionName}, + }, + { + APIGroups: []string{ + "", + }, + Resources: []string{ + "configmaps", + "secrets", // for helm + "services", + "serviceaccounts", + }, + Verbs: []string{ + "create", + "update", + "delete", + "patch", + "get", + "list", + "watch", + }, + }, + { + APIGroups: []string{ + "apiextensions.k8s.io", + }, + Resources: []string{ + "customresourcedefinitions", + }, + Verbs: []string{ + "create", + "update", + "delete", + "patch", + "get", + "list", + "watch", + }, + }, + { + APIGroups: []string{ + "apps", + }, + Resources: []string{ + "deployments", + }, + Verbs: []string{ + "create", + "update", + "delete", + "patch", + "get", + "list", + "watch", + }, + }, + { + APIGroups: []string{ + "rbac.authorization.k8s.io", + }, + Resources: []string{ + "clusterroles", + "roles", + "clusterrolebindings", + "rolebindings", + }, + Verbs: []string{ + "create", + "update", + "delete", + "patch", + "get", + "list", + "watch", + "bind", + "escalate", + }, + }, + { + APIGroups: []string{ + "networking.k8s.io", + }, + Resources: []string{ + "networkpolicies", + }, + Verbs: []string{ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete", + }, + }, + }, + } + err := c.Create(ctx, cr) + if err != nil { + return err + } + crb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: sa.Name, + Namespace: sa.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: name, + }, + } + err = c.Create(ctx, crb) + if err != nil { + return err + } + + return nil +} + +func TestInit(t *testing.T) (*ocv1.ClusterExtension, *ocv1.ClusterCatalog, *corev1.ServiceAccount, *corev1.Namespace) { + ce, cc := TestInitClusterExtensionClusterCatalog(t) + sa, ns := TestInitServiceAccountNamespace(t, ce.Name) + return ce, cc, sa, ns +} + +func TestInitClusterExtensionClusterCatalog(t *testing.T) (*ocv1.ClusterExtension, *ocv1.ClusterCatalog) { + ceName := fmt.Sprintf("clusterextension-%s", rand.String(8)) + + ce := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: ceName, + }, + } + + cc, err := CreateTestCatalog(context.Background(), testCatalogName, os.Getenv(testCatalogRefEnvVar)) + require.NoError(t, err) + + ValidateCatalogUnpack(t) + + return ce, cc +} + +func TestInitServiceAccountNamespace(t *testing.T, clusterExtensionName string) (*corev1.ServiceAccount, *corev1.Namespace) { + var err error + + ns, err := CreateNamespace(context.Background(), clusterExtensionName) + require.NoError(t, err) + + name := types.NamespacedName{ + Name: clusterExtensionName, + Namespace: ns.GetName(), + } + + sa, err := CreateServiceAccount(context.Background(), name, clusterExtensionName) + require.NoError(t, err) + + return sa, ns +} + +func ValidateCatalogUnpack(t *testing.T) { + catalog := &ocv1.ClusterCatalog{} + t.Log("Ensuring ClusterCatalog has Status.Condition of Progressing with a status == True and reason == Succeeded") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + err := c.Get(context.Background(), types.NamespacedName{Name: testCatalogName}, catalog) + require.NoError(ct, err) + cond := apimeta.FindStatusCondition(catalog.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("Checking that catalog has the expected metadata label") + require.NotNil(t, catalog.Labels) + require.Contains(t, catalog.Labels, "olm.operatorframework.io/metadata.name") + require.Equal(t, testCatalogName, catalog.Labels["olm.operatorframework.io/metadata.name"]) + + t.Log("Ensuring ClusterCatalog has Status.Condition of Type = Serving with status == True") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + err := c.Get(context.Background(), types.NamespacedName{Name: testCatalogName}, catalog) + require.NoError(ct, err) + cond := apimeta.FindStatusCondition(catalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) +} + +func EnsureNoExtensionResources(t *testing.T, clusterExtensionName string) { + ls := labels.Set{"olm.operatorframework.io/owner-name": clusterExtensionName} + + // CRDs may take an extra long time to be deleted, and may run into the following error: + // Condition=Terminating Status=True Reason=InstanceDeletionFailed Message="could not list instances: storage is (re)initializing" + t.Logf("By waiting for CustomResourceDefinitions of %q to be deleted", clusterExtensionName) + require.EventuallyWithT(t, func(ct *assert.CollectT) { + list := &apiextensionsv1.CustomResourceDefinitionList{} + err := c.List(context.Background(), list, client.MatchingLabelsSelector{Selector: ls.AsSelector()}) + require.NoError(ct, err) + require.Empty(ct, list.Items) + }, 5*pollDuration, pollInterval) + + t.Logf("By waiting for ClusterRoleBindings of %q to be deleted", clusterExtensionName) + require.EventuallyWithT(t, func(ct *assert.CollectT) { + list := &rbacv1.ClusterRoleBindingList{} + err := c.List(context.Background(), list, client.MatchingLabelsSelector{Selector: ls.AsSelector()}) + require.NoError(ct, err) + require.Empty(ct, list.Items) + }, 2*pollDuration, pollInterval) + + t.Logf("By waiting for ClusterRoles of %q to be deleted", clusterExtensionName) + require.EventuallyWithT(t, func(ct *assert.CollectT) { + list := &rbacv1.ClusterRoleList{} + err := c.List(context.Background(), list, client.MatchingLabelsSelector{Selector: ls.AsSelector()}) + require.NoError(ct, err) + require.Empty(ct, list.Items) + }, 2*pollDuration, pollInterval) +} + +func TestCleanup(t *testing.T, cat *ocv1.ClusterCatalog, clusterExtension *ocv1.ClusterExtension, sa *corev1.ServiceAccount, ns *corev1.Namespace) { + if cat != nil { + t.Logf("By deleting ClusterCatalog %q", cat.Name) + require.NoError(t, c.Delete(context.Background(), cat)) + require.Eventually(t, func() bool { + err := c.Get(context.Background(), types.NamespacedName{Name: cat.Name}, &ocv1.ClusterCatalog{}) + return errors.IsNotFound(err) + }, pollDuration, pollInterval) + } + + if clusterExtension != nil { + t.Logf("By deleting ClusterExtension %q", clusterExtension.Name) + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + require.Eventually(t, func() bool { + err := c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, &ocv1.ClusterExtension{}) + return errors.IsNotFound(err) + }, pollDuration, pollInterval) + EnsureNoExtensionResources(t, clusterExtension.Name) + } + + if sa != nil { + t.Logf("By deleting ServiceAccount %q", sa.Name) + require.NoError(t, c.Delete(context.Background(), sa)) + require.Eventually(t, func() bool { + err := c.Get(context.Background(), types.NamespacedName{Name: sa.Name, Namespace: sa.Namespace}, &corev1.ServiceAccount{}) + return errors.IsNotFound(err) + }, pollDuration, pollInterval) + } + + if ns != nil { + t.Logf("By deleting Namespace %q", ns.Name) + require.NoError(t, c.Delete(context.Background(), ns)) + require.Eventually(t, func() bool { + err := c.Get(context.Background(), types.NamespacedName{Name: ns.Name}, &corev1.Namespace{}) + return errors.IsNotFound(err) + }, pollDuration, pollInterval) + } +} + +// CreateTestCatalog will create a new catalog on the test cluster, provided +// the context, catalog name, and the image reference. It returns the created catalog +// or an error if any errors occurred while creating the catalog. +// Note that catalogd will automatically create the label: +// +// "olm.operatorframework.io/metadata.name": name +func CreateTestCatalog(ctx context.Context, name string, imageRef string) (*ocv1.ClusterCatalog, error) { + catalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: imageRef, + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + + err := c.Create(ctx, catalog) + return catalog, err +} + +func init() { + cfg = ctrl.GetConfigOrDie() + + var err error + utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme)) + c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + utilruntime.Must(err) +} diff --git a/test/regression/convert/convert_test.go b/test/regression/convert/convert_test.go new file mode 100644 index 0000000000..1e70c08cf0 --- /dev/null +++ b/test/regression/convert/convert_test.go @@ -0,0 +1,120 @@ +/* +## registry+v1 bundle regression test + +This test in convert_test.go verifies that rendering registry+v1 bundles to manifests +always produces the same files and content. + +It runs: go run generate-manifests.go -output-dir=./testdata/tmp/rendered/ +Then compares: ./testdata/tmp/rendered/ vs ./testdata/expected-manifests/ + +Files are sorted by kind/namespace/name for consistency. + +To update expected output (only on purpose), run: + + go run generate-manifests.go -output-dir=./testdata/expected-manifests/ +*/ +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" +) + +// Test_RenderedOutputMatchesExpected runs generate-manifests.go, +// then compares the generated files in ./testdata/tmp/rendered/ +// against expected-manifests/. +// It fails if any file differs or is missing. +// TMP dir is cleaned up after test ends. +func Test_RenderedOutput_MatchesExpected(t *testing.T) { + tmpRoot := "./testdata/tmp/rendered/" + expectedRoot := "./testdata/expected-manifests/" + + // Remove the temporary output directory always + t.Cleanup(func() { + _ = os.RemoveAll("./testdata/tmp") + }) + + // Call the generate-manifests.go script to generate the manifests + // in the temporary directory. + cmd := exec.Command("go", "run", "generate-manifests.go", "-output-dir="+tmpRoot) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + err := cmd.Run() + require.NoError(t, err, "failed to generate manifests") + + // Compare structure + contents + err = compareDirs(expectedRoot, tmpRoot) + require.NoError(t, err, "rendered output differs from expected") +} + +// compareDirs compares expectedRootPath and generatedRootPath directories recursively. +// It returns an error if any file is missing, extra, or has content mismatch. +// On mismatch, it includes a detailed diff using cmp.Diff. +func compareDirs(expectedRootPath, generatedRootPath string) error { + // Step 1: Ensure every expected file exists in actual and contents match + err := filepath.Walk(expectedRootPath, func(expectedPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + relPath, err := filepath.Rel(expectedRootPath, expectedPath) + if err != nil { + return err + } + actualPath := filepath.Join(generatedRootPath, relPath) + + expectedBytes, err := os.ReadFile(expectedPath) + if err != nil { + return fmt.Errorf("failed to read expected file: %s", expectedPath) + } + actualBytes, err := os.ReadFile(actualPath) + if err != nil { + return fmt.Errorf("missing file: %s", relPath) + } + + if !bytes.Equal(expectedBytes, actualBytes) { + diff := cmp.Diff(string(expectedBytes), string(actualBytes)) + return fmt.Errorf("file content mismatch at: %s\nDiff (-expected +actual):\n%s", relPath, diff) + } + return nil + }) + if err != nil { + return err + } + + // Step 2: Ensure actual does not contain unexpected files + err = filepath.Walk(generatedRootPath, func(actualPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + relPath, err := filepath.Rel(generatedRootPath, actualPath) + if err != nil { + return err + } + expectedPath := filepath.Join(expectedRootPath, relPath) + + _, err = os.Stat(expectedPath) + if os.IsNotExist(err) { + return fmt.Errorf("unexpected extra file: %s", relPath) + } else if err != nil { + return fmt.Errorf("error checking expected file: %s", expectedPath) + } + return nil + }) + return err +} diff --git a/test/regression/convert/generate-manifests.go b/test/regression/convert/generate-manifests.go new file mode 100644 index 0000000000..8af87cf6a1 --- /dev/null +++ b/test/regression/convert/generate-manifests.go @@ -0,0 +1,143 @@ +// generate-manifests.go +// +// Renders registry+v1 bundles into YAML manifests for regression testing. +// Used by tests to make sure output from the BundleRenderer stays consistent. +// +// By default, writes to ./testdata/tmp/generate/. +// To update expected output, run: +// +// go run generate-manifests.go -output-dir=./testdata/expected-manifests/ +// +// Only re-generate if you intentionally change rendering behavior. +// Note that if the test fails is likely a regression in the renderer. +package main + +import ( + "cmp" + "flag" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render" + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1" +) + +// This is a helper for a regression test to make sure the renderer output doesn't change. +// +// It renders known bundles into YAML files and writes them to a target output dir. +// By default, it writes to a temp path used in tests: +// +// ./testdata/tmp/rendered/ +// +// If you want to update the expected output, run it with: +// +// go run generate-manifests.go -output-dir=./testdata/expected-manifests/ +// +// Note: Expected output should never change unless the renderer changes which is unlikely. +// If the convert_test.go test fails, it likely means a regression was introduced in the renderer. +func main() { + bundleRootDir := "testdata/bundles/" + defaultOutputDir := "./testdata/tmp/rendered/" + outputRootDir := flag.String("output-dir", defaultOutputDir, "path to write rendered manifests to") + flag.Parse() + + if err := os.RemoveAll(*outputRootDir); err != nil { + fmt.Printf("error removing output directory: %v\n", err) + os.Exit(1) + } + + for _, tc := range []struct { + name string + installNamespace string + watchNamespace string + bundle string + testCaseName string + }{ + { + name: "AllNamespaces", + installNamespace: "argocd-system", + bundle: "argocd-operator.v0.6.0", + testCaseName: "all-namespaces", + }, { + name: "SingleNamespaces", + installNamespace: "argocd-system", + watchNamespace: "argocd-watch", + bundle: "argocd-operator.v0.6.0", + testCaseName: "single-namespace", + }, { + name: "OwnNamespaces", + installNamespace: "argocd-system", + watchNamespace: "argocd-system", + bundle: "argocd-operator.v0.6.0", + testCaseName: "own-namespace", + }, { + name: "Webhooks", + installNamespace: "webhook-system", + bundle: "webhook-operator.v0.0.5", + testCaseName: "all-webhook-types", + }, + } { + bundlePath := filepath.Join(bundleRootDir, tc.bundle) + generatedManifestPath := filepath.Join(*outputRootDir, tc.bundle, tc.testCaseName) + if err := generateManifests(generatedManifestPath, bundlePath, tc.installNamespace, tc.watchNamespace); err != nil { + fmt.Printf("Error generating manifests: %v", err) + os.Exit(1) + } + } +} + +func generateManifests(outputPath, bundleDir, installNamespace, watchNamespace string) error { + // Parse bundleFS into RegistryV1 + regv1, err := source.FromFS(os.DirFS(bundleDir)).GetBundle() + if err != nil { + fmt.Printf("error parsing bundle directory: %v\n", err) + os.Exit(1) + } + + // Convert RegistryV1 to plain manifests + objs, err := registryv1.Renderer.Render(regv1, installNamespace, render.WithTargetNamespaces(watchNamespace)) + if err != nil { + return fmt.Errorf("error converting registry+v1 bundle: %w", err) + } + + // Write plain manifests out to testcase directory + if err := os.MkdirAll(outputPath, os.ModePerm); err != nil { + return fmt.Errorf("error creating bundle directory: %w", err) + } + + if err := func() error { + for idx, obj := range slices.SortedFunc(slices.Values(objs), orderByKindNamespaceName) { + kind := obj.GetObjectKind().GroupVersionKind().Kind + fileName := fmt.Sprintf("%02d_%s_%s.yaml", idx, strings.ToLower(kind), obj.GetName()) + data, err := yaml.Marshal(obj) + if err != nil { + return err + } + if err := os.WriteFile(filepath.Join(outputPath, fileName), data, 0600); err != nil { + return err + } + } + return nil + }(); err != nil { + // Clean up output directory in case of error + _ = os.RemoveAll(outputPath) + return fmt.Errorf("error writing object files: %w", err) + } + + return nil +} + +func orderByKindNamespaceName(a client.Object, b client.Object) int { + return cmp.Or( + cmp.Compare(a.GetObjectKind().GroupVersionKind().Kind, b.GetObjectKind().GroupVersionKind().Kind), + cmp.Compare(a.GetNamespace(), b.GetNamespace()), + cmp.Compare(a.GetName(), b.GetName()), + ) +} diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-controller-manager-metrics-service_v1_service.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-controller-manager-metrics-service_v1_service.yaml new file mode 100644 index 0000000000..f800102eda --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-controller-manager-metrics-service_v1_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + control-plane: controller-manager + name: argocd-operator-controller-manager-metrics-service +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-manager-config_v1_configmap.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-manager-config_v1_configmap.yaml new file mode 100644 index 0000000000..e04f244378 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-manager-config_v1_configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +data: + controller_manager_config.yaml: | + apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 + kind: ControllerManagerConfig + health: + healthProbeBindAddress: :8081 + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + leaderElection: + leaderElect: true + resourceName: b674928d.argoproj.io +kind: ConfigMap +metadata: + name: argocd-operator-manager-config diff --git a/config/base/rbac/auth_proxy_client_clusterrole.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml similarity index 58% rename from config/base/rbac/auth_proxy_client_clusterrole.yaml rename to test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml index 51a75db47a..19a68a5707 100644 --- a/config/base/rbac/auth_proxy_client_clusterrole.yaml +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -1,9 +1,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: metrics-reader + creationTimestamp: null + name: argocd-operator-metrics-reader rules: - nonResourceURLs: - - "/metrics" + - /metrics verbs: - get diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator.v0.6.0.clusterserviceversion.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator.v0.6.0.clusterserviceversion.yaml new file mode 100644 index 0000000000..ff0f830b31 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argocd-operator.v0.6.0.clusterserviceversion.yaml @@ -0,0 +1,1193 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "AppProject", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Application", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ApplicationSet", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCD", + "metadata": { + "name": "argocd-sample" + }, + "spec": { + "controller": { + "resources": { + "limits": { + "cpu": "2000m", + "memory": "2048Mi" + }, + "requests": { + "cpu": "250m", + "memory": "1024Mi" + } + } + }, + "ha": { + "enabled": false, + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "redis": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "repo": { + "resources": { + "limits": { + "cpu": "1000m", + "memory": "512Mi" + }, + "requests": { + "cpu": "250m", + "memory": "256Mi" + } + } + }, + "server": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "125m", + "memory": "128Mi" + } + }, + "route": { + "enabled": true + } + }, + "sso": { + "dex": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "provider": "dex" + } + } + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCDExport", + "metadata": { + "name": "argocdexport-sample" + }, + "spec": { + "argocd": "argocd-sample" + } + } + ] + capabilities: Deep Insights + categories: Integration & Delivery + certified: "false" + description: Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. + operators.operatorframework.io/builder: operator-sdk-v1.10.0+git + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + repository: https://github.com/argoproj-labs/argocd-operator + containerImage: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + support: Argo CD + name: argocd-operator.v0.6.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: An Application is a group of Kubernetes resources as defined by + a manifest. + displayName: Application + kind: Application + name: applications.argoproj.io + version: v1alpha1 + - description: An ApplicationSet is a group or set of Application resources. + displayName: ApplicationSet + kind: ApplicationSet + name: applicationsets.argoproj.io + version: v1alpha1 + - description: An AppProject is a logical grouping of Argo CD Applications. + displayName: AppProject + kind: AppProject + name: appprojects.argoproj.io + version: v1alpha1 + - description: ArgoCDExport is the Schema for the argocdexports API + displayName: Argo CDExport + kind: ArgoCDExport + name: argocdexports.argoproj.io + resources: + - kind: ArgoCD + name: "" + version: v1alpha1 + - kind: ArgoCDExport + name: "" + version: v1alpha1 + - kind: ConfigMap + name: "" + version: v1 + - kind: CronJob + name: "" + version: v1 + - kind: Deployment + name: "" + version: v1 + - kind: Ingress + name: "" + version: v1 + - kind: Job + name: "" + version: v1 + - kind: PersistentVolumeClaim + name: "" + version: v1 + - kind: Pod + name: "" + version: v1 + - kind: Prometheus + name: "" + version: v1 + - kind: ReplicaSet + name: "" + version: v1 + - kind: Route + name: "" + version: v1 + - kind: Secret + name: "" + version: v1 + - kind: Service + name: "" + version: v1 + - kind: ServiceMonitor + name: "" + version: v1 + - kind: StatefulSet + name: "" + version: v1 + specDescriptors: + - description: Argocd is the name of the ArgoCD instance to export. + displayName: ArgoCD + path: argocd + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + displayName: Schedule + path: schedule + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Storage defines the storage configuration options. + displayName: Storage + path: storage + statusDescriptors: + - description: 'Phase is a simple, high-level summary of where the ArgoCDExport + is in its lifecycle. There are five possible phase values: Pending: The + ArgoCDExport has been accepted by the Kubernetes system, but one or more + of the required resources have not been created. Running: All of the containers + for the ArgoCDExport are still running, or in the process of starting or + restarting. Succeeded: All containers for the ArgoCDExport have terminated + in success, and will not be restarted. Failed: At least one container has + terminated in failure, either exited with non-zero status or was terminated + by the system. Unknown: For some reason the state of the ArgoCDExport could + not be obtained.' + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + version: v1alpha1 + - description: ArgoCD is the Schema for the argocds API + displayName: Argo CD + kind: ArgoCD + name: argocds.argoproj.io + resources: + - kind: ArgoCD + name: "" + version: v1alpha1 + - kind: ArgoCDExport + name: "" + version: v1alpha1 + - kind: ConfigMap + name: "" + version: v1 + - kind: CronJob + name: "" + version: v1 + - kind: Deployment + name: "" + version: v1 + - kind: Ingress + name: "" + version: v1 + - kind: Job + name: "" + version: v1 + - kind: PersistentVolumeClaim + name: "" + version: v1 + - kind: Pod + name: "" + version: v1 + - kind: Prometheus + name: "" + version: v1 + - kind: ReplicaSet + name: "" + version: v1 + - kind: Route + name: "" + version: v1 + - kind: Secret + name: "" + version: v1 + - kind: Service + name: "" + version: v1 + - kind: ServiceMonitor + name: "" + version: v1 + - kind: StatefulSet + name: "" + version: v1 + specDescriptors: + - description: ApplicationInstanceLabelKey is the key name where Argo CD injects + the app name as a tracking label. + displayName: Application Instance Label Key' + path: applicationInstanceLabelKey + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Host is the hostname to use for Ingress/Route resources. + displayName: Host + path: applicationSet.webhookServer.host + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:text + - description: Enabled will toggle the creation of the Ingress. + displayName: Ingress Enabled' + path: applicationSet.webhookServer.ingress.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Enabled will toggle the creation of the OpenShift Route. + displayName: Route Enabled' + path: applicationSet.webhookServer.route.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: ConfigManagementPlugins is used to specify additional config + management plugins. + displayName: Config Management Plugins' + path: configManagementPlugins + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Operation is the number of application operation processors. + displayName: Operation Processor Count' + path: controller.processors.operation + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Controller + - urn:alm:descriptor:com.tectonic.ui:number + - description: Status is the number of application status processors. + displayName: Status Processor Count' + path: controller.processors.status + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Controller + - urn:alm:descriptor:com.tectonic.ui:number + - description: Resources defines the Compute Resources required by the container + for the Application Controller. + displayName: Resource Requirements' + path: controller.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Controller + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Config is the dex connector configuration. + displayName: Configuration + path: dex.config + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: Image is the Dex container image. + displayName: Image + path: dex.image + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: OpenShiftOAuth enables OpenShift OAuth authentication for the + Dex server. + displayName: OpenShift OAuth Enabled' + path: dex.openShiftOAuth + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Resources defines the Compute Resources required by the container + for Dex. + displayName: Resource Requirements' + path: dex.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Version is the Dex container image tag. + displayName: Version + path: dex.version + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: GAAnonymizeUsers toggles user IDs being hashed before sending + to google analytics. + displayName: Google Analytics Anonymize Users' + path: gaAnonymizeUsers + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: GATrackingID is the google analytics tracking ID to use. + displayName: Google Analytics Tracking ID' + path: gaTrackingID + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Enabled will toggle Grafana support globally for ArgoCD. + displayName: Enabled + path: grafana.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Host is the hostname to use for Ingress/Route resources. + displayName: Host + path: grafana.host + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:text + - description: Image is the Grafana container image. + displayName: Image + path: grafana.image + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:text + - description: Enabled will toggle the creation of the Ingress. + displayName: Ingress Enabled' + path: grafana.ingress.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Resources defines the Compute Resources required by the container + for Grafana. + displayName: Resource Requirements' + path: grafana.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Enabled will toggle the creation of the OpenShift Route. + displayName: Route Enabled' + path: grafana.route.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Size is the replica count for the Grafana Deployment. + displayName: Size + path: grafana.size + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:podCount + - description: Version is the Grafana container image tag. + displayName: Version + path: grafana.version + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:text + - description: Enabled will toggle HA support globally for Argo CD. + displayName: Enabled + path: ha.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:HA + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: HelpChatText is the text for getting chat help, defaults to "Chat + now!" + displayName: Help Chat Text' + path: helpChatText + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: HelpChatURL is the URL for getting chat help, this will typically + be your Slack channel for support. + displayName: Help Chat URL' + path: helpChatURL + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Image is the ArgoCD container image for all ArgoCD components. + displayName: Image + path: image + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:ArgoCD + - urn:alm:descriptor:com.tectonic.ui:text + - description: Name of an ArgoCDExport from which to import data. + displayName: Name + path: import.name + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Import + - urn:alm:descriptor:com.tectonic.ui:text + - description: Namespace for the ArgoCDExport, defaults to the same namespace + as the ArgoCD. + displayName: Namespace + path: import.namespace + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Import + - urn:alm:descriptor:com.tectonic.ui:text + - description: InitialRepositories to configure Argo CD with upon creation of + the cluster. + displayName: Initial Repositories' + path: initialRepositories + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: KustomizeVersions is a listing of configured versions of Kustomize + to be made available within ArgoCD. + displayName: Kustomize Build Options' + path: kustomizeVersions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: OIDCConfig is the OIDC configuration as an alternative to dex. + displayName: OIDC Config' + path: oidcConfig + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Enabled will toggle Prometheus support globally for ArgoCD. + displayName: Enabled + path: prometheus.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Host is the hostname to use for Ingress/Route resources. + displayName: Host + path: prometheus.host + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:text + - description: Enabled will toggle the creation of the Ingress. + displayName: Ingress Enabled' + path: prometheus.ingress.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Enabled will toggle the creation of the OpenShift Route. + displayName: Route Enabled' + path: prometheus.route.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Size is the replica count for the Prometheus StatefulSet. + displayName: Size + path: prometheus.size + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:podCount + - description: DefaultPolicy is the name of the default role which Argo CD will + falls back to, when authorizing API requests (optional). If omitted or empty, + users may be still be able to login, but will see no apps, projects, etc... + displayName: Default Policy' + path: rbac.defaultPolicy + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:RBAC + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Policy is CSV containing user-defined RBAC policies and role + definitions. Policy rules are in the form: p, subject, resource, action, + object, effect Role definitions and bindings are in the form: g, subject, + inherited-subject See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md + for additional information.' + displayName: Policy + path: rbac.policy + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:RBAC + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Scopes controls which OIDC scopes to examine during rbac enforcement + (in addition to `sub` scope). If omitted, defaults to: ''[groups]''.' + displayName: Scopes + path: rbac.scopes + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:RBAC + - urn:alm:descriptor:com.tectonic.ui:text + - description: Image is the Redis container image. + displayName: Image + path: redis.image + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Redis + - urn:alm:descriptor:com.tectonic.ui:text + - description: Resources defines the Compute Resources required by the container + for Redis. + displayName: Resource Requirements' + path: redis.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Redis + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Version is the Redis container image tag. + displayName: Version + path: redis.version + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Redis + - urn:alm:descriptor:com.tectonic.ui:text + - description: Resources defines the Compute Resources required by the container + for Redis. + displayName: Resource Requirements' + path: repo.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Repo + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: ResourceActions customizes resource action behavior. + displayName: Resource Action Customizations' + path: resourceActions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: 'ResourceCustomizations customizes resource behavior. Keys are + in the form: group/Kind. Please note that this is being deprecated in favor + of ResourceHealthChecks, ResourceIgnoreDifferences, and ResourceActions.' + displayName: Resource Customizations' + path: resourceCustomizations + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: ResourceExclusions is used to completely ignore entire classes + of resource group/kinds. + displayName: Resource Exclusions' + path: resourceExclusions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: ResourceHealthChecks customizes resource health check behavior. + displayName: Resource Health Check Customizations' + path: resourceHealthChecks + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: ResourceIgnoreDifferences customizes resource ignore difference + behavior. + displayName: Resource Ignore Difference Customizations' + path: resourceIgnoreDifferences + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: ResourceTrackingMethod defines how Argo CD should track resources + that it manages + displayName: Resource Tracking Method' + path: resourceTrackingMethod + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Enabled will toggle autoscaling support for the Argo CD Server + component. + displayName: Autoscale Enabled' + path: server.autoscale.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Host is the hostname to use for Ingress/Route resources. + displayName: GRPC Host + path: server.grpc.host + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:text + - description: Ingress defines the desired state for the Argo CD Server GRPC + Ingress. + displayName: GRPC Ingress Enabled' + path: server.grpc.ingress + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Enabled will toggle the creation of the Ingress. + displayName: Ingress Enabled' + path: server.grpc.ingress.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Host is the hostname to use for Ingress/Route resources. + displayName: Host + path: server.host + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:text + - description: Enabled will toggle the creation of the Ingress. + displayName: Ingress Enabled' + path: server.ingress.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Insecure toggles the insecure flag. + displayName: Insecure + path: server.insecure + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Resources defines the Compute Resources required by the container + for the Argo CD server component. + displayName: Resource Requirements' + path: server.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Enabled will toggle the creation of the OpenShift Route. + displayName: Route Enabled' + path: server.route.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Grafana + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Prometheus + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Type is the ServiceType to use for the Service resource. + displayName: Service Type' + path: server.service.type + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Server + - urn:alm:descriptor:com.tectonic.ui:text + - description: Config is the dex connector configuration. + displayName: Configuration + path: sso.dex.config + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: Image is the Dex container image. + displayName: Image + path: sso.dex.image + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: OpenShiftOAuth enables OpenShift OAuth authentication for the + Dex server. + displayName: OpenShift OAuth Enabled' + path: sso.dex.openShiftOAuth + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Resources defines the Compute Resources required by the container + for Dex. + displayName: Resource Requirements' + path: sso.dex.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Version is the Dex container image tag. + displayName: Version + path: sso.dex.version + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:Dex + - urn:alm:descriptor:com.tectonic.ui:text + - description: StatusBadgeEnabled toggles application status badge feature. + displayName: Status Badge Enabled' + path: statusBadgeEnabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: UsersAnonymousEnabled toggles anonymous user access. The anonymous + users get default role permissions specified argocd-rbac-cm. + displayName: Anonymous Users Enabled' + path: usersAnonymousEnabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Version is the tag to use with the ArgoCD container image for + all ArgoCD components. + displayName: Version + path: version + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldGroup:ArgoCD + - urn:alm:descriptor:com.tectonic.ui:text + statusDescriptors: + - description: 'ApplicationController is a simple, high-level summary of where + the Argo CD application controller component is in its lifecycle. There + are four possible ApplicationController values: Pending: The Argo CD application + controller component has been accepted by the Kubernetes system, but one + or more of the required resources have not been created. Running: All of + the required Pods for the Argo CD application controller component are in + a Ready state. Failed: At least one of the Argo CD application controller + component Pods had a failure. Unknown: The state of the Argo CD application + controller component could not be obtained.' + displayName: ApplicationController + path: applicationController + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'ApplicationSetController is a simple, high-level summary of + where the Argo CD applicationSet controller component is in its lifecycle. + There are four possible ApplicationSetController values: Pending: The Argo + CD applicationSet controller component has been accepted by the Kubernetes + system, but one or more of the required resources have not been created. + Running: All of the required Pods for the Argo CD applicationSet controller + component are in a Ready state. Failed: At least one of the Argo CD applicationSet + controller component Pods had a failure. Unknown: The state of the Argo + CD applicationSet controller component could not be obtained.' + displayName: ApplicationSetController + path: applicationSetController + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Dex is a simple, high-level summary of where the Argo CD Dex + component is in its lifecycle. There are four possible dex values: Pending: + The Argo CD Dex component has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: All + of the required Pods for the Argo CD Dex component are in a Ready state. + Failed: At least one of the Argo CD Dex component Pods had a failure. Unknown: + The state of the Argo CD Dex component could not be obtained.' + displayName: Dex + path: dex + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'NotificationsController is a simple, high-level summary of where + the Argo CD notifications controller component is in its lifecycle. There + are four possible NotificationsController values: Pending: The Argo CD notifications + controller component has been accepted by the Kubernetes system, but one + or more of the required resources have not been created. Running: All of + the required Pods for the Argo CD notifications controller component are + in a Ready state. Failed: At least one of the Argo CD notifications controller + component Pods had a failure. Unknown: The state of the Argo CD notifications + controller component could not be obtained.' + displayName: NotificationsController + path: notificationsController + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Phase is a simple, high-level summary of where the ArgoCD is + in its lifecycle. There are four possible phase values: Pending: The ArgoCD + has been accepted by the Kubernetes system, but one or more of the required + resources have not been created. Available: All of the resources for the + ArgoCD are ready. Failed: At least one resource has experienced a failure. + Unknown: The state of the ArgoCD phase could not be obtained.' + displayName: Phase + path: phase + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Redis is a simple, high-level summary of where the Argo CD Redis + component is in its lifecycle. There are four possible redis values: Pending: + The Argo CD Redis component has been accepted by the Kubernetes system, + but one or more of the required resources have not been created. Running: + All of the required Pods for the Argo CD Redis component are in a Ready + state. Failed: At least one of the Argo CD Redis component Pods had a failure. + Unknown: The state of the Argo CD Redis component could not be obtained.' + displayName: Redis + path: redis + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Repo is a simple, high-level summary of where the Argo CD Repo + component is in its lifecycle. There are four possible repo values: Pending: + The Argo CD Repo component has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: All + of the required Pods for the Argo CD Repo component are in a Ready state. + Failed: At least one of the Argo CD Repo component Pods had a failure. + Unknown: The state of the Argo CD Repo component could not be obtained.' + displayName: Repo + path: repo + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'Server is a simple, high-level summary of where the Argo CD + server component is in its lifecycle. There are four possible server values: + Pending: The Argo CD server component has been accepted by the Kubernetes + system, but one or more of the required resources have not been created. + Running: All of the required Pods for the Argo CD server component are in + a Ready state. Failed: At least one of the Argo CD server component Pods + had a failure. Unknown: The state of the Argo CD server component could + not be obtained.' + displayName: Server + path: server + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: 'SSOConfig defines the status of SSO configuration. Success: + Only one SSO provider is configured in CR. Failed: SSO configuration is + illegal or more than one SSO providers are configured in CR. Unknown: The + SSO configuration could not be obtained.' + displayName: SSOConfig + path: ssoConfig + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + version: v1alpha1 + description: | + ## Overview + + The Argo CD Operator manages the full lifecycle for [Argo CD](https://argoproj.github.io/argo-cd/) and it's + components. The operator's goal is to automate the tasks required when operating an Argo CD cluster. + + Beyond installation, the operator helps to automate the process of upgrading, backing up and restoring as needed and + remove the human as much as possible. In addition, the operator aims to provide deep insights into the Argo CD + environment by configuring Prometheus and Grafana to aggregate, visualize and expose the metrics already exported by + Argo CD. + + The operator aims to provide the following, and is a work in progress. + + * Easy configuration and installation of the Argo CD components with sane defaults to get up and running quickly. + * Provide seamless upgrades to the Argo CD components. + * Ability to back up and restore an Argo CD cluster from a point in time or on a recurring schedule. + * Aggregate and expose the metrics for Argo CD and the operator itself using Prometheus and Grafana. + * Autoscale the Argo CD components as necessary to handle variability in demand. + + ## Usage + + Deploy a basic Argo CD cluster by creating a new ArgoCD resource in the namespace where the operator is installed. + + ``` + apiVersion: argoproj.io/v1alpha1 + kind: ArgoCD + metadata: + name: example-argocd + spec: {} + ``` + + ## Backup + + Backup the cluster above by creating a new ArgoCDExport resource in the namespace where the operator is installed. + + ``` + apiVersion: argoproj.io/v1alpha1 + kind: ArgoCDExport + metadata: + name: example-argocdexport + spec: + argocd: example-argocd + ``` + + See the [documentation](https://argocd-operator.readthedocs.io) and examples on + [github](https://github.com/argoproj-labs/argocd-operator) for more information. + displayName: Argo CD + icon: + - base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDIzIDMwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zOnNlcmlmPSJodHRwOi8vd3d3LnNlcmlmLmNvbS8iIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MjsiPgogICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwtOS4yLC03KSI+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2LDI3LjdDMTYsMjcuNyAxNS44LDI4LjMgMTUuNSwyOC42QzE1LjMsMjguOCAxNS4xLDI4LjkgMTQuOCwyOC45QzE0LjEsMjkuMSAxMy4zLDI5LjIgMTMuMywyOS4yQzEzLjMsMjkuMiAxNCwyOS4zIDE0LjgsMjkuNEMxNS4xLDI5LjQgMTUuMSwyOS40IDE1LjMsMjkuNUMxNS44LDI5LjUgMTYsMjkuMiAxNiwyOS4yTDE2LDI3LjdaIiBzdHlsZT0iZmlsbDpyZ2IoMjMzLDEwMSw3NSk7ZmlsbC1ydWxlOm5vbnplcm87Ii8+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjUuMiwyNy43QzI1LjIsMjcuNyAyNS40LDI4LjMgMjUuNywyOC42QzI1LjksMjguOCAyNi4xLDI4LjkgMjYuNCwyOC45QzI3LjEsMjkuMSAyNy45LDI5LjIgMjcuOSwyOS4yQzI3LjksMjkuMiAyNy4yLDI5LjMgMjYuMywyOS40QzI2LDI5LjQgMjYsMjkuNCAyNS44LDI5LjVDMjUuMiwyOS41IDI1LjEsMjkuMiAyNS4xLDI5LjJMMjUuMiwyNy43WiIgc3R5bGU9ImZpbGw6cmdiKDIzMywxMDEsNzUpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMjAuNyIgY3k9IjE3LjgiIHI9IjEwLjgiIHN0eWxlPSJmaWxsOnJnYigxODIsMjA3LDIzNCk7Ii8+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSIyMC43IiBjeT0iMTcuOCIgcj0iMTAuNCIgc3R5bGU9ImZpbGw6cmdiKDIzMCwyNDUsMjQ4KTsiLz4KICAgICAgICAgICAgICAgIDxjaXJjbGUgY3g9IjIwLjciIGN5PSIxOCIgcj0iOC41IiBzdHlsZT0iZmlsbDpyZ2IoMjA4LDIzMiwyNDApOyIvPgogICAgICAgICAgICAgICAgPGcgaWQ9IkJvZHlfMV8iPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNS43LDIyQzE1LjcsMjIgMTYuNCwzMy4zIDE2LjQsMzMuNUMxNi40LDMzLjYgMTYuNSwzMy44IDE2LDM0QzE1LjUsMzQuMiAxMy45LDM0LjYgMTMuOSwzNC42TDE2LjMsMzQuNkMxNy40LDM0LjYgMTcuNCwzMy43IDE3LjQsMzMuNUMxNy40LDMzLjMgMTcuNywyOSAxNy43LDI5QzE3LjcsMjkgMTcuOCwzNC4xIDE3LjgsMzQuM0MxNy44LDM0LjUgMTcuNywzNC44IDE3LDM1QzE2LjUsMzUuMSAxNSwzNS40IDE1LDM1LjRMMTcuMywzNS40QzE4LjcsMzUuNCAxOC43LDM0LjUgMTguNywzNC41TDE5LDMwQzE5LDMwIDE5LjEsMzQuNSAxOS4xLDM1QzE5LjEsMzUuNCAxOC44LDM1LjcgMTcuNywzNS45QzE3LDM2LjEgMTYuMSwzNi4zIDE2LjEsMzYuM0wxOC43LDM2LjNDMjAsMzYuMiAyMC4yLDM1LjMgMjAuMiwzNS4zTDIyLjQsMjQuMUwxNS43LDIyWiIgc3R5bGU9ImZpbGw6cmdiKDIzOCwxMjEsNzUpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yNS43LDIyQzI1LjcsMjIgMjUsMzMuMyAyNSwzMy41QzI1LDMzLjYgMjQuOSwzMy44IDI1LjQsMzRDMjUuOSwzNC4yIDI3LjUsMzQuNiAyNy41LDM0LjZMMjUuMSwzNC42QzI0LDM0LjYgMjQsMzMuNyAyNCwzMy41QzI0LDMzLjMgMjMuNywyOSAyMy43LDI5QzIzLjcsMjkgMjMuNiwzNC4xIDIzLjYsMzQuM0MyMy42LDM0LjUgMjMuNywzNC44IDI0LjQsMzVDMjQuOSwzNS4xIDI2LjQsMzUuNCAyNi40LDM1LjRMMjQuMSwzNS40QzIyLjcsMzUuNCAyMi43LDM0LjUgMjIuNywzNC41TDIyLjQsMzBDMjIuNCwzMCAyMi4zLDM0LjUgMjIuMywzNUMyMi4zLDM1LjQgMjIuNiwzNS43IDIzLjcsMzUuOUMyNC40LDM2LjEgMjUuMywzNi4zIDI1LjMsMzYuM0wyMi43LDM2LjNDMjEuNCwzNi4yIDIxLjIsMzUuMyAyMS4yLDM1LjNMMTksMjQuMUwyNS43LDIyWiIgc3R5bGU9ImZpbGw6cmdiKDIzOCwxMjEsNzUpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yNS44LDE2LjVDMjUuOCwxOS4zIDIzLjUsMjEuNSAyMC44LDIxLjVDMTguMSwyMS41IDE1LjgsMTkuMiAxNS44LDE2LjVDMTUuOCwxMy44IDE4LjEsMTEuNSAyMC44LDExLjVDMjMuNSwxMS41IDI1LjgsMTMuNyAyNS44LDE2LjVaIiBzdHlsZT0iZmlsbDpyZ2IoMjM4LDEyMSw3NSk7ZmlsbC1ydWxlOm5vbnplcm87Ii8+CiAgICAgICAgICAgICAgICAgICAgPGNsaXBQYXRoIGlkPSJfY2xpcDEiPgogICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjUuOCwxNi4zTDI1LjIsMzBMMTYuMiwzMEwxNS43LDE2LjMiLz4KICAgICAgICAgICAgICAgICAgICA8L2NsaXBQYXRoPgogICAgICAgICAgICAgICAgICAgIDxnIGNsaXAtcGF0aD0idXJsKCNfY2xpcDEpIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMjAuOCIgY3k9IjE5LjIiIHI9IjguOSIgc3R5bGU9ImZpbGw6cmdiKDIzOCwxMjEsNzUpOyIvPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjUuNSwyMkMyNS41LDIyIDI2LjEsMTYuNyAyNS4zLDE0LjdDMjMuOCwxMS4yIDIwLjMsMTEuNSAyMC4zLDExLjVDMjAuMywxMS41IDIyLjMsMTIuMyAyMi40LDE1LjNDMjIuNSwxNy40IDIyLjQsMjAuNSAyMi40LDIwLjVMMjUuNSwyMloiIHN0eWxlPSJmaWxsOnJnYigyMjcsNzgsNTkpO2ZpbGwtb3BhY2l0eTowLjIyO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPGcgaWQ9IkZhY2VfMV8iPgogICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgY3g9IjE4LjciIGN5PSIxMy44IiByPSIwLjciIHN0eWxlPSJmaWxsOnJnYigyNTEsMjIzLDE5NSk7ZmlsbC1vcGFjaXR5OjAuNTsiLz4KICAgICAgICAgICAgICAgICAgICA8Zz4KICAgICAgICAgICAgICAgICAgICAgICAgPGc+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjIuNSwyNEMyMi41LDI1LjcgMjEuNywyNi44IDIwLjcsMjYuOEMxOS43LDI2LjggMTguOSwyNS41IDE4LjksMjMuOEMxOC45LDIzLjggMTkuNywyNS40IDIwLjgsMjUuNEMyMS45LDI1LjQgMjIuNSwyNCAyMi41LDI0WiIgc3R5bGU9ImZpbGw6cmdiKDEsMSwxKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMi41LDI0QzIyLjUsMjUuMSAyMS43LDI1LjcgMjAuNywyNS43QzE5LjcsMjUuNyAxOSwyNC45IDE5LDIzLjlDMTksMjMuOSAxOS44LDI0LjkgMjAuOSwyNC45QzIyLDI0LjkgMjIuNSwyNCAyMi41LDI0WiIgc3R5bGU9ImZpbGw6d2hpdGU7ZmlsbC1ydWxlOm5vbnplcm87Ii8+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgPGc+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Zz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Zz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMjQuMiIgY3k9IjE5LjMiIHI9IjMuMSIgc3R5bGU9ImZpbGw6cmdiKDIzMywxMDEsNzUpOyIvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSIxNy4yIiBjeT0iMTkuMyIgcj0iMy4xIiBzdHlsZT0iZmlsbDpyZ2IoMjMzLDEwMSw3NSk7Ii8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSIyNC4yIiBjeT0iMTkuMyIgcj0iMi40IiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMTciIGN5PSIxOS4zIiByPSIyLjQiIHN0eWxlPSJmaWxsOndoaXRlOyIvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjaXJjbGUgY3g9IjE3IiBjeT0iMTkiIHI9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDEsMSwxKTsiLz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSIyNC4yIiBjeT0iMTkiIHI9IjAuNyIgc3R5bGU9ImZpbGw6cmdiKDEsMSwxKTsiLz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik05LjcsMjAuNUM5LjQsMjAuNSA5LjIsMjAuMyA5LjIsMjBMOS4yLDE2QzkuMiwxNS43IDkuNCwxNS41IDkuNywxNS41QzEwLDE1LjUgMTAuMiwxNS43IDEwLjIsMTZMMTAuMiwyMEMxMC4yLDIwLjMgMTAsMjAuNSA5LjcsMjAuNVoiIHN0eWxlPSJmaWxsOnJnYigxODIsMjA3LDIzNCk7ZmlsbC1ydWxlOm5vbnplcm87Ii8+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMzEuNSwyMC41QzMxLjIsMjAuNSAzMSwyMC4zIDMxLDIwTDMxLDE2QzMxLDE1LjcgMzEuMiwxNS41IDMxLjUsMTUuNUMzMS44LDE1LjUgMzIsMTUuNyAzMiwxNkwzMiwyMEMzMiwyMC4zIDMxLjgsMjAuNSAzMS41LDIwLjVaIiBzdHlsZT0iZmlsbDpyZ2IoMTgyLDIwNywyMzQpO2ZpbGwtcnVsZTpub256ZXJvOyIvPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMTcuMyIgY3k9IjkuOCIgcj0iMC41IiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMy43LDIzLjNDMTMuNiwyMy4zIDEzLjUsMjMuMyAxMy40LDIzLjJDMTIuMiwyMS43IDExLjYsMTkuOCAxMS42LDE3LjlDMTEuNiwxNi4zIDEyLDE0LjggMTIuOCwxMy40QzEzLjYsMTIuMSAxNC43LDExIDE2LDEwLjJDMTYuMiwxMC4xIDE2LjQsMTAuMiAxNi41LDEwLjNDMTYuNiwxMC41IDE2LjUsMTAuNyAxNi40LDEwLjhDMTMuOSwxMi4yIDEyLjMsMTQuOSAxMi4zLDE3LjhDMTIuMywxOS42IDEyLjksMjEuMyAxNCwyMi43QzE0LjEsMjIuOCAxNC4xLDIzLjEgMTMuOSwyMy4yQzEzLjgsMjMuMyAxMy44LDIzLjMgMTMuNywyMy4zWiIgc3R5bGU9ImZpbGw6d2hpdGU7ZmlsbC1ydWxlOm5vbnplcm87Ii8+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjUuMiwyOEwyNS4yLDI3LjJDMjMuOCwyOCAyMi4zLDI4LjggMjAuNSwyOC44QzE4LjUsMjguOCAxNy4yLDI3LjkgMTUuOSwyNy4yTDE2LDI4QzE2LDI4IDE3LjUsMjkuNiAyMC42LDI5LjZDMjMuNSwyOS41IDI1LjIsMjggMjUuMiwyOFoiIHN0eWxlPSJmaWxsOnJnYigyMzMsMTAxLDc1KTtmaWxsLW9wYWNpdHk6MC4yNTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+Cg== + mediatype: image/svg+xml + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - events + - namespaces + - persistentvolumeclaims + - pods + - secrets + - serviceaccounts + - services + - services/finalizers + verbs: + - '*' + - apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - apps + resourceNames: + - argocd-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - '*' + - apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - '*' + - apiGroups: + - argoproj.io + resources: + - argocdexports + - argocdexports/finalizers + - argocdexports/status + verbs: + - '*' + - apiGroups: + - argoproj.io + resources: + - argocds + - argocds/finalizers + - argocds/status + verbs: + - '*' + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - '*' + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - '*' + - apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - list + - watch + - apiGroups: + - monitoring.coreos.com + resources: + - prometheuses + - servicemonitors + verbs: + - '*' + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - '*' + - apiGroups: + - oauth.openshift.io + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - '*' + - apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - '*' + - apiGroups: + - template.openshift.io + resources: + - templateconfigs + - templateinstances + - templates + verbs: + - '*' + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + serviceAccountName: argocd-operator-controller-manager + deployments: + - name: argocd-operator-controller-manager + spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + strategy: {} + template: + metadata: + labels: + control-plane: controller-manager + spec: + containers: + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + resources: {} + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + securityContext: + runAsNonRoot: true + serviceAccountName: argocd-operator-controller-manager + terminationGracePeriodSeconds: 10 + permissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + serviceAccountName: argocd-operator-controller-manager + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - gitops + - kubernetes + links: + - name: Argo CD Project + url: https://argoproj.github.io/argo-cd/ + - name: Operator Documentation + url: https://argocd-operator.readthedocs.io + - name: Operator Source Code + url: https://github.com/argoproj-labs/argocd-operator + maintainers: + - email: aveerama@redhat.com + name: Abhishek Veeramalla + maturity: alpha + provider: + name: Argo CD Community + replaces: argocd-operator.v0.5.0 + version: 0.6.0 diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applications.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applications.yaml new file mode 100644 index 0000000000..136d6fb546 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applications.yaml @@ -0,0 +1,4019 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: applications.argoproj.io + app.kubernetes.io/part-of: argocd + name: applications.argoproj.io +spec: + group: argoproj.io + names: + kind: Application + listKind: ApplicationList + plural: applications + shortNames: + - app + - apps + singular: application + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.sync.status + name: Sync Status + type: string + - jsonPath: .status.health.status + name: Health Status + type: string + - jsonPath: .status.sync.revision + name: Revision + priority: 10 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Application is a definition of Application resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + operation: + description: Operation contains information about a requested or running + operation + properties: + info: + description: Info is a list of informational items for this operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was initiated + automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who started + operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent retries + of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default unit + is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed + for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply --dry-run` + without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides sync + source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from the cluster + that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall be part + of the sync + items: + description: SyncOperationResource contains resources to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version (Helm) + which to sync the application to If omitted, will use the revision + specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or chart + version (Helm) which to sync each source in sources field for + the application to If omitted, will use the revision specified + in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by + not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources for + Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be + commit, tag, or branch. If omitted, will equal to HEAD. + In case of Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the sync + properties: + apply: + description: Apply will perform a `kubectl apply` to perform + the sync. + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources to + perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + type: object + type: object + type: object + spec: + description: ApplicationSpec represents desired application state. Contains + link to repository with application definition and additional parameters + link definition revision. + properties: + destination: + description: Destination is a reference to the target Kubernetes server + and namespace + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster and + must be set to the Kubernetes control plane API + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences is a list of resources and their fields + which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter + and list of json paths which should be ignored during comparison + with live state. + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + description: ManagedFieldsManagers is a list of trusted managers. + Fields mutated by those managers will take precedence over + the desired state defined in the SCM and won't be displayed + in diffs + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + description: Info contains a list of information (URLs, email addresses, + and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a reference to the project this application + belongs to. The empty string means that application belongs to the + 'default' project. + type: string + revisionHistoryLimit: + description: RevisionHistoryLimit limits the number of items kept + in the application's revision history, which is used for informational + purposes as well as for rollbacks to previous versions. This should + only be changed in exceptional circumstances. Setting to zero will + store no history. This will reduce storage used. Increasing will + increase the space used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location of the application's + manifests or chart + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being used + during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels to + add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to force + applying common annotations to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize to + use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the location of the application's + manifests or chart + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being + used during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to + force applying common annotations to resources for Kustomize + apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of + Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + description: SyncPolicy controls when and how a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the + target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources + (default: false)' + type: boolean + prune: + description: 'Prune specifies whether to delete resources + from the cluster that are not found in the sources anymore + as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal specifes whether to revert resources + back to their desired state upon modification in the cluster + (default: false)' + type: boolean + type: object + managedNamespaceMetadata: + description: ManagedNamespaceMetadata controls metadata in the + given namespace (if CreateNamespace=true) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time + allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + status: + description: ApplicationStatus contains status information for the application + properties: + conditions: + description: Conditions is a list of currently observed application + conditions + items: + description: ApplicationCondition contains details about an application + condition, which is usally an error or warning + properties: + lastTransitionTime: + description: LastTransitionTime is the time the condition was + last observed + format: date-time + type: string + message: + description: Message contains human-readable message indicating + details about condition + type: string + type: + description: Type is an application condition type + type: string + required: + - message + - type + type: object + type: array + health: + description: Health contains information about the application's current + health status + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application or + resource + type: string + type: object + history: + description: History contains information about the application's + sync history + items: + description: RevisionHistory contains history information about + a previous sync + properties: + deployStartedAt: + description: DeployStartedAt holds the time the sync operation + started + format: date-time + type: string + deployedAt: + description: DeployedAt holds the time the sync operation completed + format: date-time + type: string + id: + description: ID is an auto incrementing identifier of the RevisionHistory + format: int64 + type: integer + revision: + description: Revision holds the revision the sync was performed + against + type: string + revisions: + description: Revisions holds the revision of each source in + sources field the sync was performed against + items: + type: string + type: array + source: + description: Source is a reference to the application source + used for the sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application sources + used for the sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - deployedAt + - id + type: object + type: array + observedAt: + description: 'ObservedAt indicates when the application state was + updated without querying latest git state Deprecated: controller + no longer updates ObservedAt field' + format: date-time + type: string + operationState: + description: OperationState contains information about any ongoing + operations, such as a sync + properties: + finishedAt: + description: FinishedAt contains time of operation completion + format: date-time + type: string + message: + description: Message holds any pertinent messages when attempting + to perform operation (typically errors). + type: string + operation: + description: Operation is the original requested operation + properties: + info: + description: Info is a list of informational items for this + operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was + initiated automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who + started operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync + fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base + duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of + time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for + retrying a failed sync. If set to 0, no retries will + be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply + --dry-run` without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides + sync source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from + the cluster that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall + be part of the sync + items: + description: SyncOperationResource contains resources + to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version + (Helm) which to sync the application to If omitted, + will use the revision specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or + chart version (Helm) which to sync each source in sources + field for the application to If omitted, will use the + revision specified in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to + Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles do + not exist locally by not appending them to helm + template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of + Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in + the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + items: + description: ApplicationSource contains all required + information about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern + to match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern + to match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific + to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles + do not exist locally by not appending them + to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter + that's passed to helm template during manifest + generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release + name to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource + definition installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to + be passed to helm template, typically defined + as a block + type: string + version: + description: Version is the Helm version to + use for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific + options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of + additional annotations to add to rendered + manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended + to resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended + to resources for Kustomize apps + type: string + version: + description: Version controls which version + of Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the + Git repository, and is only valid for applications + sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry + in the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the + variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an + array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map + type parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a + string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source + within sources field. This field will not be used + if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision + of the source to sync the application to. In case + of Git, this can be commit, tag, or branch. If + omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, + e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the + sync + properties: + apply: + description: Apply will perform a `kubectl apply` + to perform the sync. + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources + to perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + type: object + type: object + type: object + phase: + description: Phase is the current phase of the operation + type: string + retryCount: + description: RetryCount contains time of operation retries + format: int64 + type: integer + startedAt: + description: StartedAt contains time of operation start + format: date-time + type: string + syncResult: + description: SyncResult is the result of a Sync operation + properties: + resources: + description: Resources contains a list of sync result items + for each individual resource in a sync operation + items: + description: ResourceResult holds the operation result details + of a specific resource + properties: + group: + description: Group specifies the API group of the resource + type: string + hookPhase: + description: HookPhase contains the state of any operation + associated with this resource OR hook This can also + contain values for non-hook resources. + type: string + hookType: + description: HookType specifies the type of the hook. + Empty for non-hook resources + type: string + kind: + description: Kind specifies the API kind of the resource + type: string + message: + description: Message contains an informational or error + message for the last sync OR operation + type: string + name: + description: Name specifies the name of the resource + type: string + namespace: + description: Namespace specifies the target namespace + of the resource + type: string + status: + description: Status holds the final result of the sync. + Will be empty if the resources is yet to be applied/pruned + and is always zero-value for hooks + type: string + syncPhase: + description: SyncPhase indicates the particular phase + of the sync that this result was acquired in + type: string + version: + description: Version specifies the API version of the + resource + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + revision: + description: Revision holds the revision this sync operation + was performed to + type: string + revisions: + description: Revisions holds the revision this sync operation + was performed for respective indexed source in sources field + items: + type: string + type: array + source: + description: Source records the application source information + of the sync, used for comparing auto-sync + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Source records the application source information + of the sync, used for comparing auto-sync + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - revision + type: object + required: + - operation + - phase + - startedAt + type: object + reconciledAt: + description: ReconciledAt indicates when the application state was + reconciled using the latest git version + format: date-time + type: string + resourceHealthSource: + description: 'ResourceHealthSource indicates where the resource health + status is stored: inline if not set or appTree' + type: string + resources: + description: Resources is a list of Kubernetes resources managed by + this application + items: + description: 'ResourceStatus holds the current sync and health status + of a resource TODO: describe members of this type' + properties: + group: + type: string + health: + description: HealthStatus contains information about the currently + observed health state of an application or resource + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application + or resource + type: string + type: object + hook: + type: boolean + kind: + type: string + name: + type: string + namespace: + type: string + requiresPruning: + type: boolean + status: + description: SyncStatusCode is a type which represents possible + comparison results + type: string + syncWave: + format: int64 + type: integer + version: + type: string + type: object + type: array + sourceType: + description: SourceType specifies the type of this application + type: string + sourceTypes: + description: SourceTypes specifies the type of the sources included + in the application + items: + description: ApplicationSourceType specifies the type of the application's + source + type: string + type: array + summary: + description: Summary contains a list of URLs and container images + used by this application + properties: + externalURLs: + description: ExternalURLs holds all external URLs of application + child resources. + items: + type: string + type: array + images: + description: Images holds all images of application child resources. + items: + type: string + type: array + type: object + sync: + description: Sync contains information about the application's current + sync status + properties: + comparedTo: + description: ComparedTo contains information about what has been + compared + properties: + destination: + description: Destination is a reference to the application's + destination used for comparison + properties: + name: + description: Name is an alternate way of specifying the + target cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace + for the application's resources. The namespace will + only be set for namespace-scoped resources that have + not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + source: + description: Source is a reference to the application's source + used for comparison + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application's multiple + sources used for comparison + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - destination + type: object + revision: + description: Revision contains information about the revision + the comparison has been performed to + type: string + revisions: + description: Revisions contains information about the revisions + of multiple sources the comparison has been performed to + items: + type: string + type: array + status: + description: Status is the sync state of the comparison + type: string + required: + - status + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applicationsets.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applicationsets.yaml new file mode 100644 index 0000000000..1699bc829b --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_applicationsets.yaml @@ -0,0 +1,10773 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: applicationsets.argoproj.io + app.kubernetes.io/part-of: argocd + name: applicationsets.argoproj.io +spec: + group: argoproj.io + names: + kind: ApplicationSet + listKind: ApplicationSetList + plural: applicationsets + shortNames: + - appset + - appsets + singular: applicationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + type: object + merge: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + mergeKeys: + items: + type: string + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - mergeKeys + type: object + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + goTemplate: + type: boolean + strategy: + properties: + rollingSync: + properties: + steps: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + maxUpdate: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + type: object + type: + type: string + type: object + syncPolicy: + properties: + preserveResourcesOnDeletion: + type: boolean + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - template + type: object + status: + properties: + applicationStatus: + items: + properties: + application: + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + status: + type: string + step: + type: string + required: + - application + - message + - status + - step + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_appprojects.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_appprojects.yaml new file mode 100644 index 0000000000..8504d6ff02 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_appprojects.yaml @@ -0,0 +1,329 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: appprojects.argoproj.io + app.kubernetes.io/part-of: argocd + name: appprojects.argoproj.io +spec: + group: argoproj.io + names: + kind: AppProject + listKind: AppProjectList + plural: appprojects + shortNames: + - appproj + - appprojs + singular: appproject + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'AppProject provides a logical grouping of applications, providing + controls for: * where the apps may deploy to (cluster whitelist) * what + may be deployed (repository whitelist, resource whitelist/blacklist) * who + can access these applications (roles, OIDC group claims bindings) * and + what they can do (RBAC policies) * automation access to these roles (JWT + tokens)' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppProjectSpec is the specification of an AppProject + properties: + clusterResourceBlacklist: + description: ClusterResourceBlacklist contains list of blacklisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + clusterResourceWhitelist: + description: ClusterResourceWhitelist contains list of whitelisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + description: + description: Description contains optional project description + type: string + destinations: + description: Destinations contains list of destinations available + for deployment + items: + description: ApplicationDestination holds information about the + application's destination + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + type: array + namespaceResourceBlacklist: + description: NamespaceResourceBlacklist contains list of blacklisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + namespaceResourceWhitelist: + description: NamespaceResourceWhitelist contains list of whitelisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + orphanedResources: + description: OrphanedResources specifies if controller should monitor + orphaned resources of apps in this project + properties: + ignore: + description: Ignore contains a list of resources that are to be + excluded from orphaned resources monitoring + items: + description: OrphanedResourceKey is a reference to a resource + to be ignored from + properties: + group: + type: string + kind: + type: string + name: + type: string + type: object + type: array + warn: + description: Warn indicates if warning condition should be created + for apps which have orphaned resources + type: boolean + type: object + permitOnlyProjectScopedClusters: + description: PermitOnlyProjectScopedClusters determines whether destinations + can only reference clusters which are project-scoped + type: boolean + roles: + description: Roles are user defined RBAC roles associated with this + project + items: + description: ProjectRole represents a role that has access to a + project + properties: + description: + description: Description is a description of the role + type: string + groups: + description: Groups are a list of OIDC group claims bound to + this role + items: + type: string + type: array + jwtTokens: + description: JWTTokens are a list of generated JWT tokens bound + to this role + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + name: + description: Name is a name for this role + type: string + policies: + description: Policies Stores a list of casbin formatted strings + that define access policies for the role in the project + items: + type: string + type: array + required: + - name + type: object + type: array + signatureKeys: + description: SignatureKeys contains a list of PGP key IDs that commits + in Git must be signed with in order to be allowed for sync + items: + description: SignatureKey is the specification of a key required + to verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sourceRepos: + description: SourceRepos contains list of repository URLs which can + be used for deployment + items: + type: string + type: array + syncWindows: + description: SyncWindows controls when syncs can be run for apps in + this project + items: + description: SyncWindow contains the kind, time, duration and attributes + that are used to assign the syncWindows to apps + properties: + applications: + description: Applications contains a list of applications that + the window will apply to + items: + type: string + type: array + clusters: + description: Clusters contains a list of clusters that the window + will apply to + items: + type: string + type: array + duration: + description: Duration is the amount of time the sync window + will be open + type: string + kind: + description: Kind defines if the window allows or blocks syncs + type: string + manualSync: + description: ManualSync enables manual syncs when they would + otherwise be blocked + type: boolean + namespaces: + description: Namespaces contains a list of namespaces that the + window will apply to + items: + type: string + type: array + schedule: + description: Schedule is the time the window will begin, specified + in cron format + type: string + timeZone: + description: TimeZone of the sync that will be applied to the + schedule + type: string + type: object + type: array + type: object + status: + description: AppProjectStatus contains status information for AppProject + CRs + properties: + jwtTokensByRole: + additionalProperties: + description: JWTTokens represents a list of JWT tokens + properties: + items: + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + type: object + description: JWTTokensByRole contains a list of JWT tokens issued + for a given role + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocdexports.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocdexports.yaml new file mode 100644 index 0000000000..8a8b0b0f18 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocdexports.yaml @@ -0,0 +1,258 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: argocdexports.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCDExport + listKind: ArgoCDExportList + plural: argocdexports + singular: argocdexport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDExport is the Schema for the argocdexports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDExportSpec defines the desired state of ArgoCDExport + properties: + argocd: + description: Argocd is the name of the ArgoCD instance to export. + type: string + image: + description: Image is the container image to use for the export Job. + type: string + schedule: + description: Schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + type: string + storage: + description: Storage defines the storage configuration options. + properties: + backend: + description: Backend defines the storage backend to use, must + be "local" (the default), "aws", "azure" or "gcp". + type: string + pvc: + description: PVC is the desired characteristics for a PersistentVolumeClaim. + properties: + accessModes: + description: 'AccessModes contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify either: * + An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified data + source, it will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have the + same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which to populate + the volume with data, if a non-empty volume is desired. + This may be any local object from a non-empty API group + (non core object) or a PersistentVolumeClaim object. When + this field is specified, volume binding will only succeed + if the type of the specified object matches some installed + volume populator or dynamic provisioner. This field will + replace the functionality of the DataSource field and as + such if both fields are non-empty, they must have the same + value. For backwards compatibility, both fields (DataSource + and DataSourceRef) will be set to the same value automatically + if one of them is empty and the other is non-empty. There + are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed values (dropping + them), DataSourceRef preserves all values, and generates + an error if a disallowed value is specified. (Alpha) Using + this field requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum resources the + volume should have. If RecoverVolumeExpansionFailure feature + is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher + than capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + secretName: + description: SecretName is the name of a Secret with encryption + key, credentials, etc. + type: string + type: object + version: + description: Version is the tag/digest to use for the export Job container + image. + type: string + required: + - argocd + type: object + status: + description: ArgoCDExportStatus defines the observed state of ArgoCDExport + properties: + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCDExport + is in its lifecycle. There are five possible phase values: Pending: + The ArgoCDExport has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: + All of the containers for the ArgoCDExport are still running, or + in the process of starting or restarting. Succeeded: All containers + for the ArgoCDExport have terminated in success, and will not be + restarted. Failed: At least one container has terminated in failure, + either exited with non-zero status or was terminated by the system. + Unknown: For some reason the state of the ArgoCDExport could not + be obtained.' + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocds.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocds.yaml new file mode 100644 index 0000000000..9b86bd9495 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/manifests/argoproj.io_argocds.yaml @@ -0,0 +1,6444 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: argocds.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCD + listKind: ArgoCDList + plural: argocds + singular: argocd + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCD is the Schema for the argocds API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDSpec defines the desired state of ArgoCD + properties: + applicationInstanceLabelKey: + description: ApplicationInstanceLabelKey is the key name where Argo + CD injects the app name as a tracking label. + type: string + applicationSet: + description: ArgoCDApplicationSet defines whether the Argo CD ApplicationSet + controller should be installed. + properties: + env: + description: Env lets you specify environment for applicationSet + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: ExtraCommandArgs allows users to pass command line + arguments to ApplicationSet controller. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the Argo CD ApplicationSet image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the ApplicationSet controller. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for ApplicationSet. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD ApplicationSet image tag. + (optional) + type: string + webhookServer: + description: WebhookServerSpec defines the options for the ApplicationSet + Webhook Server component. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + use for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the + Route resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the + contents of the ca certificate of the final destination. When + using reencrypt termination this file should be + provided in order to have routers use it for health + checks on the secure connection. If this field is + not specified, the router may provide its own destination + CA and perform hostname validation using the short + service name (service.namespace.svc), which allows + infrastructure generated certificates to automatically + verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to + a route. While each router may make its own decisions + on which ports to expose, this is normally port + 80. \n * Allow - traffic is sent to the server on + the insecure port (default) * Disable - no traffic + is allowed on the insecure port. * Redirect - clients + are redirected to the secure port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + type: object + type: object + banner: + description: Banner defines an additional banner to be displayed in + Argo CD UI + properties: + content: + description: Content defines the banner message content to display + type: string + url: + description: URL defines an optional URL to be used as banner + message link + type: string + required: + - content + type: object + configManagementPlugins: + description: ConfigManagementPlugins is used to specify additional + config management plugins. + type: string + controller: + description: Controller defines the Application Controller options + for ArgoCD. + properties: + appSync: + description: "AppSync is used to control the sync frequency, by + default the ArgoCD controller polls Git every 3m. \n Set this + to a duration, e.g. 10m or 600s to control the synchronisation + frequency." + type: string + env: + description: Env lets you specify environment for application + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + logFormat: + description: LogFormat refers to the log format used by the Application + Controller component. Defaults to ArgoCDDefaultLogFormat if + not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level used by the Application + Controller component. Defaults to ArgoCDDefaultLogLevel if not + configured. Valid options are debug, info, error, and warn. + type: string + parallelismLimit: + description: ParallelismLimit defines the limit for parallel kubectl + operations + format: int32 + type: integer + processors: + description: Processors contains the options for the Application + Controller processors. + properties: + operation: + description: Operation is the number of application operation + processors. + format: int32 + type: integer + status: + description: Status is the number of application status processors. + format: int32 + type: integer + type: object + resources: + description: Resources defines the Compute Resources required + by the container for the Application Controller. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + sharding: + description: Sharding contains the options for the Application + Controller sharding configuration. + properties: + enabled: + description: Enabled defines whether sharding should be enabled + on the Application Controller component. + type: boolean + replicas: + description: Replicas defines the number of replicas to run + in the Application controller shard. + format: int32 + type: integer + type: object + type: object + dex: + description: Dex defines the Dex server options for ArgoCD. + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must be a + member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + disableAdmin: + description: DisableAdmin will disable the admin user. + type: boolean + extraConfig: + additionalProperties: + type: string + description: "ExtraConfig can be used to add fields to Argo CD configmap + that are not supported by Argo CD CRD. \n Note: ExtraConfig takes + precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin` + = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true. + In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"] + = true." + type: object + gaAnonymizeUsers: + description: GAAnonymizeUsers toggles user IDs being hashed before + sending to google analytics. + type: boolean + gaTrackingID: + description: GATrackingID is the google analytics tracking ID to use. + type: string + grafana: + description: Grafana defines the Grafana server options for ArgoCD. + properties: + enabled: + description: Enabled will toggle Grafana support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + image: + description: Image is the Grafana container image. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + resources: + description: Resources defines the Compute Resources required + by the container for Grafana. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Grafana Deployment. + format: int32 + type: integer + version: + description: Version is the Grafana container image tag. + type: string + required: + - enabled + type: object + ha: + description: HA options for High Availability support for the Redis + component. + properties: + enabled: + description: Enabled will toggle HA support globally for Argo + CD. + type: boolean + redisProxyImage: + description: RedisProxyImage is the Redis HAProxy container image. + type: string + redisProxyVersion: + description: RedisProxyVersion is the Redis HAProxy container + image tag. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for HA. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - enabled + type: object + helpChatText: + description: HelpChatText is the text for getting chat help, defaults + to "Chat now!" + type: string + helpChatURL: + description: HelpChatURL is the URL for getting chat help, this will + typically be your Slack channel for support. + type: string + image: + description: Image is the ArgoCD container image for all ArgoCD components. + type: string + import: + description: Import is the import/restore options for ArgoCD. + properties: + name: + description: Name of an ArgoCDExport from which to import data. + type: string + namespace: + description: Namespace for the ArgoCDExport, defaults to the same + namespace as the ArgoCD. + type: string + required: + - name + type: object + initialRepositories: + description: InitialRepositories to configure Argo CD with upon creation + of the cluster. + type: string + initialSSHKnownHosts: + description: InitialSSHKnownHosts defines the SSH known hosts data + upon creation of the cluster for connecting Git repositories via + SSH. + properties: + excludedefaulthosts: + description: ExcludeDefaultHosts describes whether you would like + to include the default list of SSH Known Hosts provided by ArgoCD. + type: boolean + keys: + description: Keys describes a custom set of SSH Known Hosts that + you would like to have included in your ArgoCD server. + type: string + type: object + kustomizeBuildOptions: + description: KustomizeBuildOptions is used to specify build options/parameters + to use with `kustomize build`. + type: string + kustomizeVersions: + description: KustomizeVersions is a listing of configured versions + of Kustomize to be made available within ArgoCD. + items: + description: KustomizeVersionSpec is used to specify information + about a kustomize version to be used within ArgoCD. + properties: + path: + description: Path is the path to a configured kustomize version + on the filesystem of your repo server. + type: string + version: + description: Version is a configured kustomize version in the + format of vX.Y.Z + type: string + type: object + type: array + monitoring: + description: Monitoring defines whether workload status monitoring + configuration for this instance. + properties: + enabled: + description: Enabled defines whether workload status monitoring + is enabled for this instance or not + type: boolean + required: + - enabled + type: object + nodePlacement: + description: NodePlacement defines NodeSelectors and Taints for Argo + CD workloads + properties: + nodeSelector: + additionalProperties: + type: string + description: NodeSelector is a field of PodSpec, it is a map of + key value pairs used for node selection + type: object + tolerations: + description: Tolerations allow the pods to schedule onto nodes + with matching taints + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + notifications: + description: Notifications defines whether the Argo CD Notifications + controller should be installed. + properties: + enabled: + description: Enabled defines whether argocd-notifications controller + should be deployed or not + type: boolean + env: + description: Env let you specify environment variables for Notifications + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is the Argo CD Notifications image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the argocd-notifications. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas to run for + notifications-controller + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Argo CD Notifications. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD Notifications image tag. (optional) + type: string + required: + - enabled + type: object + oidcConfig: + description: OIDCConfig is the OIDC configuration as an alternative + to dex. + type: string + prometheus: + description: Prometheus defines the Prometheus server options for + ArgoCD. + properties: + enabled: + description: Enabled will toggle Prometheus support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Prometheus StatefulSet. + format: int32 + type: integer + required: + - enabled + type: object + rbac: + description: RBAC defines the RBAC configuration for Argo CD. + properties: + defaultPolicy: + description: DefaultPolicy is the name of the default role which + Argo CD will falls back to, when authorizing API requests (optional). + If omitted or empty, users may be still be able to login, but + will see no apps, projects, etc... + type: string + policy: + description: 'Policy is CSV containing user-defined RBAC policies + and role definitions. Policy rules are in the form: p, subject, + resource, action, object, effect Role definitions and bindings + are in the form: g, subject, inherited-subject See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md + for additional information.' + type: string + policyMatcherMode: + description: PolicyMatcherMode configures the matchers function + mode for casbin. There are two options for this, 'glob' for + glob matcher or 'regex' for regex matcher. + type: string + scopes: + description: 'Scopes controls which OIDC scopes to examine during + rbac enforcement (in addition to `sub` scope). If omitted, defaults + to: ''[groups]''.' + type: string + type: object + redis: + description: Redis defines the Redis server options for ArgoCD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the redis server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + disableTLSVerification: + description: DisableTLSVerification defines whether redis server + API should be accessed using strict TLS validation + type: boolean + image: + description: Image is the Redis container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Redis container image tag. + type: string + type: object + repo: + description: Repo defines the repo server options for Argo CD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the repo server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + env: + description: Env lets you specify environment for repo server + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + execTimeout: + description: ExecTimeout specifies the timeout in seconds for + tool execution + type: integer + extraRepoCommandArgs: + description: Extra Command arguments allows users to pass command + line arguments to repo server workload. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraRepoCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the ArgoCD Repo Server container image. + type: string + initContainers: + description: InitContainers defines the list of initialization + containers for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + logFormat: + description: LogFormat describes the log format that should be + used by the Repo Server. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the Repo Server. Defaults to ArgoCDDefaultLogLevel if not + set. Valid options are debug, info, error, and warn. + type: string + mountsatoken: + description: MountSAToken describes whether you would like to + have the Repo server mount the service account token + type: boolean + replicas: + description: Replicas defines the number of replicas for argocd-repo-server. + Value should be greater than or equal to 0. Default is nil. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + serviceaccount: + description: ServiceAccount defines the ServiceAccount user that + you would like the Repo server to use + type: string + sidecarContainers: + description: SidecarContainers defines the list of sidecar containers + for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + verifytls: + description: VerifyTLS defines whether repo server API should + be accessed using strict TLS validation + type: boolean + version: + description: Version is the ArgoCD Repo Server container image + tag. + type: string + volumeMounts: + description: VolumeMounts adds volumeMounts to the repo server + container + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: Volumes adds volumes to the repo server deployment + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob + disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure + Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of + Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the ConfigMap, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed + to the associated CSI driver which will determine + the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all + containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "Ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is + tied to the pod that defines it - it will be created before + the pod starts, and deleted when the pod is removed. \n + Use this if: a) the volume is only needed while the pod + runs, b) features of normal volumes like restoring from + snapshot or capacity tracking are needed, c) the storage + driver is specified through a storage class, and d) the + storage driver supports dynamic volume provisioning through + \ a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'AccessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify + either: * An existing VolumeSnapshot object + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) If + the provisioner or an external controller + can support the specified data source, it + will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always + have the same contents as the DataSourceRef + field.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which + to populate the volume with data, if a non-empty + volume is desired. This may be any local object + from a non-empty API group (non core object) + or a PersistentVolumeClaim object. When this + field is specified, volume binding will only + succeed if the type of the specified object + matches some installed volume populator or + dynamic provisioner. This field will replace + the functionality of the DataSource field + and as such if both fields are non-empty, + they must have the same value. For backwards + compatibility, both fields (DataSource and + DataSourceRef) will be set to the same value + automatically if one of them is empty and + the other is non-empty. There are two important + differences between DataSource and DataSourceRef: + * While DataSource only allows two specific + types of objects, DataSourceRef allows any + non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed + values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed + value is specified. (Alpha) Using this field + requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum + resources the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify + resource requirements that are lower than + previous value but must still be higher than + capacity recorded in the status field of the + claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: VolumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: FC represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use + for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to + pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the + plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. + Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the volume + directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'Path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will + be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI + transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is + either an IP or ip_addr:port if the port is other + than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits used to set permissions on created + files by default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. Directories within the + path are not affected by this setting. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in + the ScaleIO system that is associated with this volume + source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start + with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys + must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + repositoryCredentials: + description: RepositoryCredentials are the Git pull credentials to + configure Argo CD with upon creation of the cluster. + type: string + resourceActions: + description: ResourceActions customizes resource action behavior. + items: + description: Resource Customization for custom action + properties: + action: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceCustomizations: + description: 'ResourceCustomizations customizes resource behavior. + Keys are in the form: group/Kind. Please note that this is being + deprecated in favor of ResourceHealthChecks, ResourceIgnoreDifferences, + and ResourceActions.' + type: string + resourceExclusions: + description: ResourceExclusions is used to completely ignore entire + classes of resource group/kinds. + type: string + resourceHealthChecks: + description: ResourceHealthChecks customizes resource health check + behavior. + items: + description: Resource Customization for custom health check + properties: + check: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceIgnoreDifferences: + description: ResourceIgnoreDifferences customizes resource ignore + difference behavior. + properties: + all: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + resourceIdentifiers: + items: + description: Resource Customization fields for ignore difference + properties: + customization: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + group: + type: string + kind: + type: string + type: object + type: array + type: object + resourceInclusions: + description: ResourceInclusions is used to only include specific group/kinds + in the reconciliation process. + type: string + resourceTrackingMethod: + description: ResourceTrackingMethod defines how Argo CD should track + resources that it manages + type: string + server: + description: Server defines the options for the ArgoCD Server component. + properties: + autoscale: + description: Autoscale defines the autoscale options for the Argo + CD Server component. + properties: + enabled: + description: Enabled will toggle autoscaling support for the + Argo CD Server component. + type: boolean + hpa: + description: HPA defines the HorizontalPodAutoscaler options + for the Argo CD Server component. + properties: + maxReplicas: + description: upper limit for the number of pods that can + be set by the autoscaler; cannot be smaller than MinReplicas. + format: int32 + type: integer + minReplicas: + description: minReplicas is the lower limit for the number + of replicas to which the autoscaler can scale down. It + defaults to 1 pod. minReplicas is allowed to be 0 if + the alpha feature gate HPAScaleToZero is enabled and + at least one Object or External metric is configured. Scaling + is active as long as at least one metric value is available. + format: int32 + type: integer + scaleTargetRef: + description: reference to scaled resource; horizontal + pod autoscaler will learn the current resource consumption + and will set the desired number of pods by using its + Scale subresource. + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + required: + - kind + - name + type: object + targetCPUUtilizationPercentage: + description: target average CPU utilization (represented + as a percentage of requested CPU) over all the pods; + if not specified the default autoscaling policy will + be used. + format: int32 + type: integer + required: + - maxReplicas + - scaleTargetRef + type: object + required: + - enabled + type: object + env: + description: Env lets you specify environment for API server pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: Extra Command arguments that would append to the + Argo CD server command. ExtraCommandArgs will not be added, + if one of these commands is already part of the server command + with same or different value. + items: + type: string + type: array + grpc: + description: GRPC defines the state for the Argo CD Server GRPC + options. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for the Argo + CD Server GRPC Ingress. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + type: object + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + insecure: + description: Insecure toggles the insecure flag. + type: boolean + logFormat: + description: LogFormat refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogLevel if + not set. Valid options are debug, info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas for argocd-server. + Default is nil. Value should be greater than or equal to 0. + Value will be ignored if Autoscaler is enabled. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for the Argo CD server component. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + service: + description: Service defines the options for the Service backing + the ArgoCD Server component. + properties: + type: + description: Type is the ServiceType to use for the Service + resource. + type: string + required: + - type + type: object + type: object + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sso: + description: SSO defines the Single Sign-on configuration for Argo + CD + properties: + dex: + description: Dex contains the configuration for Argo CD dex authentication + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must + be a member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + image: + description: Image is the SSO container image. + type: string + keycloak: + description: Keycloak contains the configuration for Argo CD keycloak + authentication + properties: + image: + description: Image is the Keycloak container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Keycloak. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + rootCA: + description: Custom root CA certificate for communicating + with the Keycloak OIDC provider + type: string + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the Keycloak container image tag. + type: string + type: object + provider: + description: Provider installs and configures the given SSO Provider + with Argo CD. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for SSO. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the SSO container image tag. + type: string + type: object + statusBadgeEnabled: + description: StatusBadgeEnabled toggles application status badge feature. + type: boolean + tls: + description: TLS defines the TLS options for ArgoCD. + properties: + ca: + description: CA defines the CA options. + properties: + configMapName: + description: ConfigMapName is the name of the ConfigMap containing + the CA Certificate. + type: string + secretName: + description: SecretName is the name of the Secret containing + the CA Certificate and Key. + type: string + type: object + initialCerts: + additionalProperties: + type: string + description: InitialCerts defines custom TLS certificates upon + creation of the cluster for connecting Git repositories via + HTTPS. + type: object + type: object + usersAnonymousEnabled: + description: UsersAnonymousEnabled toggles anonymous user access. + The anonymous users get default role permissions specified argocd-rbac-cm. + type: boolean + version: + description: Version is the tag to use with the ArgoCD container image + for all ArgoCD components. + type: string + type: object + status: + description: ArgoCDStatus defines the observed state of ArgoCD + properties: + applicationController: + description: 'ApplicationController is a simple, high-level summary + of where the Argo CD application controller component is in its + lifecycle. There are four possible ApplicationController values: + Pending: The Argo CD application controller component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD application controller component are in a Ready state. Failed: + At least one of the Argo CD application controller component Pods + had a failure. Unknown: The state of the Argo CD application controller + component could not be obtained.' + type: string + applicationSetController: + description: 'ApplicationSetController is a simple, high-level summary + of where the Argo CD applicationSet controller component is in its + lifecycle. There are four possible ApplicationSetController values: + Pending: The Argo CD applicationSet controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD applicationSet controller component are in a Ready + state. Failed: At least one of the Argo CD applicationSet controller + component Pods had a failure. Unknown: The state of the Argo CD + applicationSet controller component could not be obtained.' + type: string + dex: + description: 'Dex is a simple, high-level summary of where the Argo + CD Dex component is in its lifecycle. There are four possible dex + values: Pending: The Argo CD Dex component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Dex component are in a Ready state. Failed: At least one + of the Argo CD Dex component Pods had a failure. Unknown: The state + of the Argo CD Dex component could not be obtained.' + type: string + host: + description: Host is the hostname of the Ingress. + type: string + notificationsController: + description: 'NotificationsController is a simple, high-level summary + of where the Argo CD notifications controller component is in its + lifecycle. There are four possible NotificationsController values: + Pending: The Argo CD notifications controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD notifications controller component are in a Ready + state. Failed: At least one of the Argo CD notifications controller + component Pods had a failure. Unknown: The state of the Argo CD + notifications controller component could not be obtained.' + type: string + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCD + is in its lifecycle. There are four possible phase values: Pending: + The ArgoCD has been accepted by the Kubernetes system, but one or + more of the required resources have not been created. Available: + All of the resources for the ArgoCD are ready. Failed: At least + one resource has experienced a failure. Unknown: The state of the + ArgoCD phase could not be obtained.' + type: string + redis: + description: 'Redis is a simple, high-level summary of where the Argo + CD Redis component is in its lifecycle. There are four possible + redis values: Pending: The Argo CD Redis component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Redis component are in a Ready state. Failed: At least one + of the Argo CD Redis component Pods had a failure. Unknown: The + state of the Argo CD Redis component could not be obtained.' + type: string + redisTLSChecksum: + description: RedisTLSChecksum contains the SHA256 checksum of the + latest known state of tls.crt and tls.key in the argocd-operator-redis-tls + secret. + type: string + repo: + description: 'Repo is a simple, high-level summary of where the Argo + CD Repo component is in its lifecycle. There are four possible repo + values: Pending: The Argo CD Repo component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Repo component are in a Ready state. Failed: At least one + of the Argo CD Repo component Pods had a failure. Unknown: The + state of the Argo CD Repo component could not be obtained.' + type: string + repoTLSChecksum: + description: RepoTLSChecksum contains the SHA256 checksum of the latest + known state of tls.crt and tls.key in the argocd-repo-server-tls + secret. + type: string + server: + description: 'Server is a simple, high-level summary of where the + Argo CD server component is in its lifecycle. There are four possible + server values: Pending: The Argo CD server component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD server component are in a Ready state. Failed: At least + one of the Argo CD server component Pods had a failure. Unknown: + The state of the Argo CD server component could not be obtained.' + type: string + ssoConfig: + description: 'SSOConfig defines the status of SSO configuration. Success: + Only one SSO provider is configured in CR. Failed: SSO configuration + is illegal or more than one SSO providers are configured in CR. + Unknown: The SSO configuration could not be obtained.' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/metadata/annotations.yaml b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/metadata/annotations.yaml new file mode 100644 index 0000000000..cdb5ed4a93 --- /dev/null +++ b/test/regression/convert/testdata/bundles/argocd-operator.v0.6.0/metadata/annotations.yaml @@ -0,0 +1,7 @@ +annotations: + operators.operatorframework.io.bundle.channel.default.v1: alpha + operators.operatorframework.io.bundle.channels.v1: alpha + operators.operatorframework.io.bundle.manifests.v1: manifests/ + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.metadata.v1: metadata/ + operators.operatorframework.io.bundle.package.v1: argocd-operator diff --git a/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook-operator.clusterserviceversion.yaml b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook-operator.clusterserviceversion.yaml new file mode 100644 index 0000000000..0ba867a2d1 --- /dev/null +++ b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook-operator.clusterserviceversion.yaml @@ -0,0 +1,282 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "WebhookTest", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "webhook-operator" + }, + "name": "webhooktest-sample" + }, + "spec": null + }, + { + "apiVersion": "webhook.operators.coreos.io/v2", + "kind": "WebhookTest", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "webhook-operator" + }, + "name": "webhooktest-sample" + }, + "spec": null + } + ] + capabilities: Basic Install + operators.operatorframework.io/builder: operator-sdk-v1.41.1 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + name: webhook-operator.v0.0.5 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: WebhookTest is the Schema for the webhooktests API + displayName: Webhook Test + kind: WebhookTest + name: webhooktests.webhook.operators.coreos.io + version: v1 + - description: WebhookTest is the Schema for the webhooktests API + displayName: Webhook Test + kind: WebhookTest + name: webhooktests.webhook.operators.coreos.io + version: v2 + description: webhook-operator test fixture + displayName: webhook-operator + icon: + - base64data: "" + mediatype: "" + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests/finalizers + verbs: + - update + - apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests/status + verbs: + - get + - patch + - update + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + serviceAccountName: webhook-operator-controller-manager + deployments: + - label: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + name: webhook-operator-controller-manager + spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + spec: + containers: + - args: + - --metrics-bind-address=:8443 + - --leader-elect + - --health-probe-bind-address=:8081 + - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs + command: + - /manager + image: quay.io/olmtest/webhook-operator:v0.0.5 + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: webhook-certs + readOnly: true + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccountName: webhook-operator-controller-manager + terminationGracePeriodSeconds: 10 + volumes: + - name: webhook-certs + secret: + secretName: webhook-server-cert + permissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + serviceAccountName: webhook-operator-controller-manager + strategy: deployment + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - test + - operator + - webhooks + links: + - name: Webhook Operator + url: https://webhook-operator.domain + maintainers: + - email: no-reply@operator-framework.io + name: no-reply + maturity: alpha + provider: + name: operator-framework + version: 0.0.5 + webhookdefinitions: + - admissionReviewVersions: + - v1 + containerPort: 443 + conversionCRDs: + - webhooktests.webhook.operators.coreos.io + deploymentName: webhook-operator-controller-manager + generateName: cwebhooktests.kb.io + sideEffects: None + targetPort: 9443 + type: ConversionWebhook + webhookPath: /convert + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: webhook-operator-controller-manager + failurePolicy: Fail + generateName: mwebhooktest-v1.kb.io + rules: + - apiGroups: + - webhook.operators.coreos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - webhooktests + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-webhook-operators-coreos-io-v1-webhooktest + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: webhook-operator-controller-manager + failurePolicy: Fail + generateName: vwebhooktest-v1.kb.io + rules: + - apiGroups: + - webhook.operators.coreos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - webhooktests + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-webhook-operators-coreos-io-v1-webhooktest diff --git a/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook.operators.coreos.io_webhooktests.yaml b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook.operators.coreos.io_webhooktests.yaml new file mode 100644 index 0000000000..a5b6926337 --- /dev/null +++ b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/manifests/webhook.operators.coreos.io_webhooktests.yaml @@ -0,0 +1,272 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: webhook-operator-system/webhook-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.19.0 + creationTimestamp: null + name: webhooktests.webhook.operators.coreos.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: webhook-operator-webhook-service + namespace: webhook-operator-system + path: /convert + conversionReviewVersions: + - v1 + group: webhook.operators.coreos.io + names: + kind: WebhookTest + listKind: WebhookTestList + plural: webhooktests + singular: webhooktest + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: WebhookTest is the Schema for the webhooktests API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of WebhookTest + properties: + mutate: + description: Mutate is a field that will be set to true by the mutating + webhook. + type: boolean + valid: + description: Valid must be set to true or the validation webhook will + reject the resource. + type: boolean + required: + - valid + type: object + status: + description: status defines the observed state of WebhookTest + properties: + conditions: + description: |- + conditions represent the current state of the WebhookTest resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: WebhookTest is the Schema for the webhooktests API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of WebhookTest + properties: + conversion: + description: Conversion is an example field of WebhookTest. Edit WebhookTest_types.go + to remove/update + properties: + mutate: + description: Mutate is a field that will be set to true by the + mutating webhook. + type: boolean + valid: + description: Valid must be set to true or the validation webhook + will reject the resource. + type: boolean + required: + - valid + type: object + required: + - conversion + type: object + status: + description: status defines the observed state of WebhookTest + properties: + conditions: + description: |- + conditions represent the current state of the WebhookTest resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/metadata/annotations.yaml b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/metadata/annotations.yaml new file mode 100644 index 0000000000..4c5cf5f0fe --- /dev/null +++ b/test/regression/convert/testdata/bundles/webhook-operator.v0.0.5/metadata/annotations.yaml @@ -0,0 +1,10 @@ +annotations: + # Core bundle annotations. + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.manifests.v1: manifests/ + operators.operatorframework.io.bundle.metadata.v1: metadata/ + operators.operatorframework.io.bundle.package.v1: webhook-operator + operators.operatorframework.io.bundle.channels.v1: alpha + operators.operatorframework.io.metrics.builder: operator-sdk-v1.41.1 + operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 + operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/00_clusterrole_argocd-operator-metrics-reader.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/00_clusterrole_argocd-operator-metrics-reader.yaml new file mode 100644 index 0000000000..19a68a5707 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/00_clusterrole_argocd-operator-metrics-reader.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: argocd-operator-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..d90e1d44b5 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,159 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - events + - namespaces + - persistentvolumeclaims + - pods + - secrets + - serviceaccounts + - services + - services/finalizers + verbs: + - '*' +- apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get +- apiGroups: + - apps + resources: + - daemonsets + - deployments + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - apps + resourceNames: + - argocd-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocdexports + - argocdexports/finalizers + - argocdexports/status + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocds + - argocds/finalizers + - argocds/status + verbs: + - '*' +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - '*' +- apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - '*' +- apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - list + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - prometheuses + - servicemonitors + verbs: + - '*' +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - '*' +- apiGroups: + - oauth.openshift.io + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - '*' +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - '*' +- apiGroups: + - template.openshift.io + resources: + - templateconfigs + - templateinstances + - templates + verbs: + - '*' +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/02_clusterrole_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/02_clusterrole_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml new file mode 100644 index 0000000000..3abef0594a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/02_clusterrole_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml @@ -0,0 +1,44 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/03_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/03_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..e1752b965a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/03_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/04_clusterrolebinding_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/04_clusterrolebinding_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml new file mode 100644 index 0000000000..5edc498bbc --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/04_clusterrolebinding_argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-operator.v0.-3gkm3u8zfarktdile5wekso69zs9bgzb988mhjm0y6p +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/05_configmap_argocd-operator-manager-config.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/05_configmap_argocd-operator-manager-config.yaml new file mode 100644 index 0000000000..98d1f72c07 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/05_configmap_argocd-operator-manager-config.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +data: + controller_manager_config.yaml: | + apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 + kind: ControllerManagerConfig + health: + healthProbeBindAddress: :8081 + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + leaderElection: + leaderElect: true + resourceName: b674928d.argoproj.io +kind: ConfigMap +metadata: + name: argocd-operator-manager-config + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/06_customresourcedefinition_applications.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/06_customresourcedefinition_applications.argoproj.io.yaml new file mode 100644 index 0000000000..b1f398ec1b --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/06_customresourcedefinition_applications.argoproj.io.yaml @@ -0,0 +1,4018 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applications.argoproj.io + app.kubernetes.io/part-of: argocd + name: applications.argoproj.io +spec: + group: argoproj.io + names: + kind: Application + listKind: ApplicationList + plural: applications + shortNames: + - app + - apps + singular: application + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.sync.status + name: Sync Status + type: string + - jsonPath: .status.health.status + name: Health Status + type: string + - jsonPath: .status.sync.revision + name: Revision + priority: 10 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Application is a definition of Application resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + operation: + description: Operation contains information about a requested or running + operation + properties: + info: + description: Info is a list of informational items for this operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was initiated + automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who started + operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent retries + of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default unit + is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed + for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply --dry-run` + without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides sync + source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from the cluster + that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall be part + of the sync + items: + description: SyncOperationResource contains resources to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version (Helm) + which to sync the application to If omitted, will use the revision + specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or chart + version (Helm) which to sync each source in sources field for + the application to If omitted, will use the revision specified + in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by + not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources for + Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be + commit, tag, or branch. If omitted, will equal to HEAD. + In case of Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the sync + properties: + apply: + description: Apply will perform a `kubectl apply` to perform + the sync. + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources to + perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + type: object + type: object + type: object + spec: + description: ApplicationSpec represents desired application state. Contains + link to repository with application definition and additional parameters + link definition revision. + properties: + destination: + description: Destination is a reference to the target Kubernetes server + and namespace + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster and + must be set to the Kubernetes control plane API + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences is a list of resources and their fields + which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter + and list of json paths which should be ignored during comparison + with live state. + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + description: ManagedFieldsManagers is a list of trusted managers. + Fields mutated by those managers will take precedence over + the desired state defined in the SCM and won't be displayed + in diffs + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + description: Info contains a list of information (URLs, email addresses, + and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a reference to the project this application + belongs to. The empty string means that application belongs to the + 'default' project. + type: string + revisionHistoryLimit: + description: RevisionHistoryLimit limits the number of items kept + in the application's revision history, which is used for informational + purposes as well as for rollbacks to previous versions. This should + only be changed in exceptional circumstances. Setting to zero will + store no history. This will reduce storage used. Increasing will + increase the space used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location of the application's + manifests or chart + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being used + during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels to + add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to force + applying common annotations to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize to + use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the location of the application's + manifests or chart + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being + used during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to + force applying common annotations to resources for Kustomize + apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of + Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + description: SyncPolicy controls when and how a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the + target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources + (default: false)' + type: boolean + prune: + description: 'Prune specifies whether to delete resources + from the cluster that are not found in the sources anymore + as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal specifes whether to revert resources + back to their desired state upon modification in the cluster + (default: false)' + type: boolean + type: object + managedNamespaceMetadata: + description: ManagedNamespaceMetadata controls metadata in the + given namespace (if CreateNamespace=true) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time + allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + status: + description: ApplicationStatus contains status information for the application + properties: + conditions: + description: Conditions is a list of currently observed application + conditions + items: + description: ApplicationCondition contains details about an application + condition, which is usally an error or warning + properties: + lastTransitionTime: + description: LastTransitionTime is the time the condition was + last observed + format: date-time + type: string + message: + description: Message contains human-readable message indicating + details about condition + type: string + type: + description: Type is an application condition type + type: string + required: + - message + - type + type: object + type: array + health: + description: Health contains information about the application's current + health status + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application or + resource + type: string + type: object + history: + description: History contains information about the application's + sync history + items: + description: RevisionHistory contains history information about + a previous sync + properties: + deployStartedAt: + description: DeployStartedAt holds the time the sync operation + started + format: date-time + type: string + deployedAt: + description: DeployedAt holds the time the sync operation completed + format: date-time + type: string + id: + description: ID is an auto incrementing identifier of the RevisionHistory + format: int64 + type: integer + revision: + description: Revision holds the revision the sync was performed + against + type: string + revisions: + description: Revisions holds the revision of each source in + sources field the sync was performed against + items: + type: string + type: array + source: + description: Source is a reference to the application source + used for the sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application sources + used for the sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - deployedAt + - id + type: object + type: array + observedAt: + description: 'ObservedAt indicates when the application state was + updated without querying latest git state Deprecated: controller + no longer updates ObservedAt field' + format: date-time + type: string + operationState: + description: OperationState contains information about any ongoing + operations, such as a sync + properties: + finishedAt: + description: FinishedAt contains time of operation completion + format: date-time + type: string + message: + description: Message holds any pertinent messages when attempting + to perform operation (typically errors). + type: string + operation: + description: Operation is the original requested operation + properties: + info: + description: Info is a list of informational items for this + operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was + initiated automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who + started operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync + fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base + duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of + time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for + retrying a failed sync. If set to 0, no retries will + be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply + --dry-run` without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides + sync source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from + the cluster that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall + be part of the sync + items: + description: SyncOperationResource contains resources + to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version + (Helm) which to sync the application to If omitted, + will use the revision specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or + chart version (Helm) which to sync each source in sources + field for the application to If omitted, will use the + revision specified in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to + Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles do + not exist locally by not appending them to helm + template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of + Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in + the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + items: + description: ApplicationSource contains all required + information about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern + to match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern + to match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific + to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles + do not exist locally by not appending them + to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter + that's passed to helm template during manifest + generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release + name to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource + definition installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to + be passed to helm template, typically defined + as a block + type: string + version: + description: Version is the Helm version to + use for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific + options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of + additional annotations to add to rendered + manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended + to resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended + to resources for Kustomize apps + type: string + version: + description: Version controls which version + of Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the + Git repository, and is only valid for applications + sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry + in the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the + variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an + array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map + type parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a + string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source + within sources field. This field will not be used + if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision + of the source to sync the application to. In case + of Git, this can be commit, tag, or branch. If + omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, + e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the + sync + properties: + apply: + description: Apply will perform a `kubectl apply` + to perform the sync. + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources + to perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + type: object + type: object + type: object + phase: + description: Phase is the current phase of the operation + type: string + retryCount: + description: RetryCount contains time of operation retries + format: int64 + type: integer + startedAt: + description: StartedAt contains time of operation start + format: date-time + type: string + syncResult: + description: SyncResult is the result of a Sync operation + properties: + resources: + description: Resources contains a list of sync result items + for each individual resource in a sync operation + items: + description: ResourceResult holds the operation result details + of a specific resource + properties: + group: + description: Group specifies the API group of the resource + type: string + hookPhase: + description: HookPhase contains the state of any operation + associated with this resource OR hook This can also + contain values for non-hook resources. + type: string + hookType: + description: HookType specifies the type of the hook. + Empty for non-hook resources + type: string + kind: + description: Kind specifies the API kind of the resource + type: string + message: + description: Message contains an informational or error + message for the last sync OR operation + type: string + name: + description: Name specifies the name of the resource + type: string + namespace: + description: Namespace specifies the target namespace + of the resource + type: string + status: + description: Status holds the final result of the sync. + Will be empty if the resources is yet to be applied/pruned + and is always zero-value for hooks + type: string + syncPhase: + description: SyncPhase indicates the particular phase + of the sync that this result was acquired in + type: string + version: + description: Version specifies the API version of the + resource + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + revision: + description: Revision holds the revision this sync operation + was performed to + type: string + revisions: + description: Revisions holds the revision this sync operation + was performed for respective indexed source in sources field + items: + type: string + type: array + source: + description: Source records the application source information + of the sync, used for comparing auto-sync + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Source records the application source information + of the sync, used for comparing auto-sync + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - revision + type: object + required: + - operation + - phase + - startedAt + type: object + reconciledAt: + description: ReconciledAt indicates when the application state was + reconciled using the latest git version + format: date-time + type: string + resourceHealthSource: + description: 'ResourceHealthSource indicates where the resource health + status is stored: inline if not set or appTree' + type: string + resources: + description: Resources is a list of Kubernetes resources managed by + this application + items: + description: 'ResourceStatus holds the current sync and health status + of a resource TODO: describe members of this type' + properties: + group: + type: string + health: + description: HealthStatus contains information about the currently + observed health state of an application or resource + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application + or resource + type: string + type: object + hook: + type: boolean + kind: + type: string + name: + type: string + namespace: + type: string + requiresPruning: + type: boolean + status: + description: SyncStatusCode is a type which represents possible + comparison results + type: string + syncWave: + format: int64 + type: integer + version: + type: string + type: object + type: array + sourceType: + description: SourceType specifies the type of this application + type: string + sourceTypes: + description: SourceTypes specifies the type of the sources included + in the application + items: + description: ApplicationSourceType specifies the type of the application's + source + type: string + type: array + summary: + description: Summary contains a list of URLs and container images + used by this application + properties: + externalURLs: + description: ExternalURLs holds all external URLs of application + child resources. + items: + type: string + type: array + images: + description: Images holds all images of application child resources. + items: + type: string + type: array + type: object + sync: + description: Sync contains information about the application's current + sync status + properties: + comparedTo: + description: ComparedTo contains information about what has been + compared + properties: + destination: + description: Destination is a reference to the application's + destination used for comparison + properties: + name: + description: Name is an alternate way of specifying the + target cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace + for the application's resources. The namespace will + only be set for namespace-scoped resources that have + not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + source: + description: Source is a reference to the application's source + used for comparison + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application's multiple + sources used for comparison + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - destination + type: object + revision: + description: Revision contains information about the revision + the comparison has been performed to + type: string + revisions: + description: Revisions contains information about the revisions + of multiple sources the comparison has been performed to + items: + type: string + type: array + status: + description: Status is the sync state of the comparison + type: string + required: + - status + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/07_customresourcedefinition_applicationsets.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/07_customresourcedefinition_applicationsets.argoproj.io.yaml new file mode 100644 index 0000000000..272bd9e056 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/07_customresourcedefinition_applicationsets.argoproj.io.yaml @@ -0,0 +1,10772 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applicationsets.argoproj.io + app.kubernetes.io/part-of: argocd + name: applicationsets.argoproj.io +spec: + group: argoproj.io + names: + kind: ApplicationSet + listKind: ApplicationSetList + plural: applicationsets + shortNames: + - appset + - appsets + singular: applicationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + type: object + merge: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + mergeKeys: + items: + type: string + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - mergeKeys + type: object + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + goTemplate: + type: boolean + strategy: + properties: + rollingSync: + properties: + steps: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + maxUpdate: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + type: object + type: + type: string + type: object + syncPolicy: + properties: + preserveResourcesOnDeletion: + type: boolean + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - template + type: object + status: + properties: + applicationStatus: + items: + properties: + application: + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + status: + type: string + step: + type: string + required: + - application + - message + - status + - step + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/08_customresourcedefinition_appprojects.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/08_customresourcedefinition_appprojects.argoproj.io.yaml new file mode 100644 index 0000000000..1ed93a159d --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/08_customresourcedefinition_appprojects.argoproj.io.yaml @@ -0,0 +1,328 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: appprojects.argoproj.io + app.kubernetes.io/part-of: argocd + name: appprojects.argoproj.io +spec: + group: argoproj.io + names: + kind: AppProject + listKind: AppProjectList + plural: appprojects + shortNames: + - appproj + - appprojs + singular: appproject + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'AppProject provides a logical grouping of applications, providing + controls for: * where the apps may deploy to (cluster whitelist) * what + may be deployed (repository whitelist, resource whitelist/blacklist) * who + can access these applications (roles, OIDC group claims bindings) * and + what they can do (RBAC policies) * automation access to these roles (JWT + tokens)' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppProjectSpec is the specification of an AppProject + properties: + clusterResourceBlacklist: + description: ClusterResourceBlacklist contains list of blacklisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + clusterResourceWhitelist: + description: ClusterResourceWhitelist contains list of whitelisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + description: + description: Description contains optional project description + type: string + destinations: + description: Destinations contains list of destinations available + for deployment + items: + description: ApplicationDestination holds information about the + application's destination + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + type: array + namespaceResourceBlacklist: + description: NamespaceResourceBlacklist contains list of blacklisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + namespaceResourceWhitelist: + description: NamespaceResourceWhitelist contains list of whitelisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + orphanedResources: + description: OrphanedResources specifies if controller should monitor + orphaned resources of apps in this project + properties: + ignore: + description: Ignore contains a list of resources that are to be + excluded from orphaned resources monitoring + items: + description: OrphanedResourceKey is a reference to a resource + to be ignored from + properties: + group: + type: string + kind: + type: string + name: + type: string + type: object + type: array + warn: + description: Warn indicates if warning condition should be created + for apps which have orphaned resources + type: boolean + type: object + permitOnlyProjectScopedClusters: + description: PermitOnlyProjectScopedClusters determines whether destinations + can only reference clusters which are project-scoped + type: boolean + roles: + description: Roles are user defined RBAC roles associated with this + project + items: + description: ProjectRole represents a role that has access to a + project + properties: + description: + description: Description is a description of the role + type: string + groups: + description: Groups are a list of OIDC group claims bound to + this role + items: + type: string + type: array + jwtTokens: + description: JWTTokens are a list of generated JWT tokens bound + to this role + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + name: + description: Name is a name for this role + type: string + policies: + description: Policies Stores a list of casbin formatted strings + that define access policies for the role in the project + items: + type: string + type: array + required: + - name + type: object + type: array + signatureKeys: + description: SignatureKeys contains a list of PGP key IDs that commits + in Git must be signed with in order to be allowed for sync + items: + description: SignatureKey is the specification of a key required + to verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sourceRepos: + description: SourceRepos contains list of repository URLs which can + be used for deployment + items: + type: string + type: array + syncWindows: + description: SyncWindows controls when syncs can be run for apps in + this project + items: + description: SyncWindow contains the kind, time, duration and attributes + that are used to assign the syncWindows to apps + properties: + applications: + description: Applications contains a list of applications that + the window will apply to + items: + type: string + type: array + clusters: + description: Clusters contains a list of clusters that the window + will apply to + items: + type: string + type: array + duration: + description: Duration is the amount of time the sync window + will be open + type: string + kind: + description: Kind defines if the window allows or blocks syncs + type: string + manualSync: + description: ManualSync enables manual syncs when they would + otherwise be blocked + type: boolean + namespaces: + description: Namespaces contains a list of namespaces that the + window will apply to + items: + type: string + type: array + schedule: + description: Schedule is the time the window will begin, specified + in cron format + type: string + timeZone: + description: TimeZone of the sync that will be applied to the + schedule + type: string + type: object + type: array + type: object + status: + description: AppProjectStatus contains status information for AppProject + CRs + properties: + jwtTokensByRole: + additionalProperties: + description: JWTTokens represents a list of JWT tokens + properties: + items: + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + type: object + description: JWTTokensByRole contains a list of JWT tokens issued + for a given role + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/09_customresourcedefinition_argocdexports.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/09_customresourcedefinition_argocdexports.argoproj.io.yaml new file mode 100644 index 0000000000..c3248d4740 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/09_customresourcedefinition_argocdexports.argoproj.io.yaml @@ -0,0 +1,257 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocdexports.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCDExport + listKind: ArgoCDExportList + plural: argocdexports + singular: argocdexport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDExport is the Schema for the argocdexports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDExportSpec defines the desired state of ArgoCDExport + properties: + argocd: + description: Argocd is the name of the ArgoCD instance to export. + type: string + image: + description: Image is the container image to use for the export Job. + type: string + schedule: + description: Schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + type: string + storage: + description: Storage defines the storage configuration options. + properties: + backend: + description: Backend defines the storage backend to use, must + be "local" (the default), "aws", "azure" or "gcp". + type: string + pvc: + description: PVC is the desired characteristics for a PersistentVolumeClaim. + properties: + accessModes: + description: 'AccessModes contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify either: * + An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified data + source, it will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have the + same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which to populate + the volume with data, if a non-empty volume is desired. + This may be any local object from a non-empty API group + (non core object) or a PersistentVolumeClaim object. When + this field is specified, volume binding will only succeed + if the type of the specified object matches some installed + volume populator or dynamic provisioner. This field will + replace the functionality of the DataSource field and as + such if both fields are non-empty, they must have the same + value. For backwards compatibility, both fields (DataSource + and DataSourceRef) will be set to the same value automatically + if one of them is empty and the other is non-empty. There + are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed values (dropping + them), DataSourceRef preserves all values, and generates + an error if a disallowed value is specified. (Alpha) Using + this field requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum resources the + volume should have. If RecoverVolumeExpansionFailure feature + is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher + than capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + secretName: + description: SecretName is the name of a Secret with encryption + key, credentials, etc. + type: string + type: object + version: + description: Version is the tag/digest to use for the export Job container + image. + type: string + required: + - argocd + type: object + status: + description: ArgoCDExportStatus defines the observed state of ArgoCDExport + properties: + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCDExport + is in its lifecycle. There are five possible phase values: Pending: + The ArgoCDExport has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: + All of the containers for the ArgoCDExport are still running, or + in the process of starting or restarting. Succeeded: All containers + for the ArgoCDExport have terminated in success, and will not be + restarted. Failed: At least one container has terminated in failure, + either exited with non-zero status or was terminated by the system. + Unknown: For some reason the state of the ArgoCDExport could not + be obtained.' + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/10_customresourcedefinition_argocds.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/10_customresourcedefinition_argocds.argoproj.io.yaml new file mode 100644 index 0000000000..426a186d4e --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/10_customresourcedefinition_argocds.argoproj.io.yaml @@ -0,0 +1,6443 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocds.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCD + listKind: ArgoCDList + plural: argocds + singular: argocd + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCD is the Schema for the argocds API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDSpec defines the desired state of ArgoCD + properties: + applicationInstanceLabelKey: + description: ApplicationInstanceLabelKey is the key name where Argo + CD injects the app name as a tracking label. + type: string + applicationSet: + description: ArgoCDApplicationSet defines whether the Argo CD ApplicationSet + controller should be installed. + properties: + env: + description: Env lets you specify environment for applicationSet + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: ExtraCommandArgs allows users to pass command line + arguments to ApplicationSet controller. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the Argo CD ApplicationSet image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the ApplicationSet controller. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for ApplicationSet. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD ApplicationSet image tag. + (optional) + type: string + webhookServer: + description: WebhookServerSpec defines the options for the ApplicationSet + Webhook Server component. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + use for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the + Route resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the + contents of the ca certificate of the final destination. When + using reencrypt termination this file should be + provided in order to have routers use it for health + checks on the secure connection. If this field is + not specified, the router may provide its own destination + CA and perform hostname validation using the short + service name (service.namespace.svc), which allows + infrastructure generated certificates to automatically + verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to + a route. While each router may make its own decisions + on which ports to expose, this is normally port + 80. \n * Allow - traffic is sent to the server on + the insecure port (default) * Disable - no traffic + is allowed on the insecure port. * Redirect - clients + are redirected to the secure port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + type: object + type: object + banner: + description: Banner defines an additional banner to be displayed in + Argo CD UI + properties: + content: + description: Content defines the banner message content to display + type: string + url: + description: URL defines an optional URL to be used as banner + message link + type: string + required: + - content + type: object + configManagementPlugins: + description: ConfigManagementPlugins is used to specify additional + config management plugins. + type: string + controller: + description: Controller defines the Application Controller options + for ArgoCD. + properties: + appSync: + description: "AppSync is used to control the sync frequency, by + default the ArgoCD controller polls Git every 3m. \n Set this + to a duration, e.g. 10m or 600s to control the synchronisation + frequency." + type: string + env: + description: Env lets you specify environment for application + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + logFormat: + description: LogFormat refers to the log format used by the Application + Controller component. Defaults to ArgoCDDefaultLogFormat if + not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level used by the Application + Controller component. Defaults to ArgoCDDefaultLogLevel if not + configured. Valid options are debug, info, error, and warn. + type: string + parallelismLimit: + description: ParallelismLimit defines the limit for parallel kubectl + operations + format: int32 + type: integer + processors: + description: Processors contains the options for the Application + Controller processors. + properties: + operation: + description: Operation is the number of application operation + processors. + format: int32 + type: integer + status: + description: Status is the number of application status processors. + format: int32 + type: integer + type: object + resources: + description: Resources defines the Compute Resources required + by the container for the Application Controller. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + sharding: + description: Sharding contains the options for the Application + Controller sharding configuration. + properties: + enabled: + description: Enabled defines whether sharding should be enabled + on the Application Controller component. + type: boolean + replicas: + description: Replicas defines the number of replicas to run + in the Application controller shard. + format: int32 + type: integer + type: object + type: object + dex: + description: Dex defines the Dex server options for ArgoCD. + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must be a + member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + disableAdmin: + description: DisableAdmin will disable the admin user. + type: boolean + extraConfig: + additionalProperties: + type: string + description: "ExtraConfig can be used to add fields to Argo CD configmap + that are not supported by Argo CD CRD. \n Note: ExtraConfig takes + precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin` + = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true. + In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"] + = true." + type: object + gaAnonymizeUsers: + description: GAAnonymizeUsers toggles user IDs being hashed before + sending to google analytics. + type: boolean + gaTrackingID: + description: GATrackingID is the google analytics tracking ID to use. + type: string + grafana: + description: Grafana defines the Grafana server options for ArgoCD. + properties: + enabled: + description: Enabled will toggle Grafana support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + image: + description: Image is the Grafana container image. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + resources: + description: Resources defines the Compute Resources required + by the container for Grafana. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Grafana Deployment. + format: int32 + type: integer + version: + description: Version is the Grafana container image tag. + type: string + required: + - enabled + type: object + ha: + description: HA options for High Availability support for the Redis + component. + properties: + enabled: + description: Enabled will toggle HA support globally for Argo + CD. + type: boolean + redisProxyImage: + description: RedisProxyImage is the Redis HAProxy container image. + type: string + redisProxyVersion: + description: RedisProxyVersion is the Redis HAProxy container + image tag. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for HA. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - enabled + type: object + helpChatText: + description: HelpChatText is the text for getting chat help, defaults + to "Chat now!" + type: string + helpChatURL: + description: HelpChatURL is the URL for getting chat help, this will + typically be your Slack channel for support. + type: string + image: + description: Image is the ArgoCD container image for all ArgoCD components. + type: string + import: + description: Import is the import/restore options for ArgoCD. + properties: + name: + description: Name of an ArgoCDExport from which to import data. + type: string + namespace: + description: Namespace for the ArgoCDExport, defaults to the same + namespace as the ArgoCD. + type: string + required: + - name + type: object + initialRepositories: + description: InitialRepositories to configure Argo CD with upon creation + of the cluster. + type: string + initialSSHKnownHosts: + description: InitialSSHKnownHosts defines the SSH known hosts data + upon creation of the cluster for connecting Git repositories via + SSH. + properties: + excludedefaulthosts: + description: ExcludeDefaultHosts describes whether you would like + to include the default list of SSH Known Hosts provided by ArgoCD. + type: boolean + keys: + description: Keys describes a custom set of SSH Known Hosts that + you would like to have included in your ArgoCD server. + type: string + type: object + kustomizeBuildOptions: + description: KustomizeBuildOptions is used to specify build options/parameters + to use with `kustomize build`. + type: string + kustomizeVersions: + description: KustomizeVersions is a listing of configured versions + of Kustomize to be made available within ArgoCD. + items: + description: KustomizeVersionSpec is used to specify information + about a kustomize version to be used within ArgoCD. + properties: + path: + description: Path is the path to a configured kustomize version + on the filesystem of your repo server. + type: string + version: + description: Version is a configured kustomize version in the + format of vX.Y.Z + type: string + type: object + type: array + monitoring: + description: Monitoring defines whether workload status monitoring + configuration for this instance. + properties: + enabled: + description: Enabled defines whether workload status monitoring + is enabled for this instance or not + type: boolean + required: + - enabled + type: object + nodePlacement: + description: NodePlacement defines NodeSelectors and Taints for Argo + CD workloads + properties: + nodeSelector: + additionalProperties: + type: string + description: NodeSelector is a field of PodSpec, it is a map of + key value pairs used for node selection + type: object + tolerations: + description: Tolerations allow the pods to schedule onto nodes + with matching taints + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + notifications: + description: Notifications defines whether the Argo CD Notifications + controller should be installed. + properties: + enabled: + description: Enabled defines whether argocd-notifications controller + should be deployed or not + type: boolean + env: + description: Env let you specify environment variables for Notifications + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is the Argo CD Notifications image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the argocd-notifications. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas to run for + notifications-controller + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Argo CD Notifications. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD Notifications image tag. (optional) + type: string + required: + - enabled + type: object + oidcConfig: + description: OIDCConfig is the OIDC configuration as an alternative + to dex. + type: string + prometheus: + description: Prometheus defines the Prometheus server options for + ArgoCD. + properties: + enabled: + description: Enabled will toggle Prometheus support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Prometheus StatefulSet. + format: int32 + type: integer + required: + - enabled + type: object + rbac: + description: RBAC defines the RBAC configuration for Argo CD. + properties: + defaultPolicy: + description: DefaultPolicy is the name of the default role which + Argo CD will falls back to, when authorizing API requests (optional). + If omitted or empty, users may be still be able to login, but + will see no apps, projects, etc... + type: string + policy: + description: 'Policy is CSV containing user-defined RBAC policies + and role definitions. Policy rules are in the form: p, subject, + resource, action, object, effect Role definitions and bindings + are in the form: g, subject, inherited-subject See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md + for additional information.' + type: string + policyMatcherMode: + description: PolicyMatcherMode configures the matchers function + mode for casbin. There are two options for this, 'glob' for + glob matcher or 'regex' for regex matcher. + type: string + scopes: + description: 'Scopes controls which OIDC scopes to examine during + rbac enforcement (in addition to `sub` scope). If omitted, defaults + to: ''[groups]''.' + type: string + type: object + redis: + description: Redis defines the Redis server options for ArgoCD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the redis server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + disableTLSVerification: + description: DisableTLSVerification defines whether redis server + API should be accessed using strict TLS validation + type: boolean + image: + description: Image is the Redis container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Redis container image tag. + type: string + type: object + repo: + description: Repo defines the repo server options for Argo CD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the repo server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + env: + description: Env lets you specify environment for repo server + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + execTimeout: + description: ExecTimeout specifies the timeout in seconds for + tool execution + type: integer + extraRepoCommandArgs: + description: Extra Command arguments allows users to pass command + line arguments to repo server workload. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraRepoCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the ArgoCD Repo Server container image. + type: string + initContainers: + description: InitContainers defines the list of initialization + containers for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + logFormat: + description: LogFormat describes the log format that should be + used by the Repo Server. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the Repo Server. Defaults to ArgoCDDefaultLogLevel if not + set. Valid options are debug, info, error, and warn. + type: string + mountsatoken: + description: MountSAToken describes whether you would like to + have the Repo server mount the service account token + type: boolean + replicas: + description: Replicas defines the number of replicas for argocd-repo-server. + Value should be greater than or equal to 0. Default is nil. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + serviceaccount: + description: ServiceAccount defines the ServiceAccount user that + you would like the Repo server to use + type: string + sidecarContainers: + description: SidecarContainers defines the list of sidecar containers + for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + verifytls: + description: VerifyTLS defines whether repo server API should + be accessed using strict TLS validation + type: boolean + version: + description: Version is the ArgoCD Repo Server container image + tag. + type: string + volumeMounts: + description: VolumeMounts adds volumeMounts to the repo server + container + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: Volumes adds volumes to the repo server deployment + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob + disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure + Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of + Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the ConfigMap, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed + to the associated CSI driver which will determine + the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all + containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "Ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is + tied to the pod that defines it - it will be created before + the pod starts, and deleted when the pod is removed. \n + Use this if: a) the volume is only needed while the pod + runs, b) features of normal volumes like restoring from + snapshot or capacity tracking are needed, c) the storage + driver is specified through a storage class, and d) the + storage driver supports dynamic volume provisioning through + \ a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'AccessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify + either: * An existing VolumeSnapshot object + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) If + the provisioner or an external controller + can support the specified data source, it + will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always + have the same contents as the DataSourceRef + field.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which + to populate the volume with data, if a non-empty + volume is desired. This may be any local object + from a non-empty API group (non core object) + or a PersistentVolumeClaim object. When this + field is specified, volume binding will only + succeed if the type of the specified object + matches some installed volume populator or + dynamic provisioner. This field will replace + the functionality of the DataSource field + and as such if both fields are non-empty, + they must have the same value. For backwards + compatibility, both fields (DataSource and + DataSourceRef) will be set to the same value + automatically if one of them is empty and + the other is non-empty. There are two important + differences between DataSource and DataSourceRef: + * While DataSource only allows two specific + types of objects, DataSourceRef allows any + non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed + values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed + value is specified. (Alpha) Using this field + requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum + resources the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify + resource requirements that are lower than + previous value but must still be higher than + capacity recorded in the status field of the + claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: VolumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: FC represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use + for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to + pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the + plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. + Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the volume + directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'Path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will + be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI + transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is + either an IP or ip_addr:port if the port is other + than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits used to set permissions on created + files by default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. Directories within the + path are not affected by this setting. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in + the ScaleIO system that is associated with this volume + source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start + with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys + must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + repositoryCredentials: + description: RepositoryCredentials are the Git pull credentials to + configure Argo CD with upon creation of the cluster. + type: string + resourceActions: + description: ResourceActions customizes resource action behavior. + items: + description: Resource Customization for custom action + properties: + action: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceCustomizations: + description: 'ResourceCustomizations customizes resource behavior. + Keys are in the form: group/Kind. Please note that this is being + deprecated in favor of ResourceHealthChecks, ResourceIgnoreDifferences, + and ResourceActions.' + type: string + resourceExclusions: + description: ResourceExclusions is used to completely ignore entire + classes of resource group/kinds. + type: string + resourceHealthChecks: + description: ResourceHealthChecks customizes resource health check + behavior. + items: + description: Resource Customization for custom health check + properties: + check: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceIgnoreDifferences: + description: ResourceIgnoreDifferences customizes resource ignore + difference behavior. + properties: + all: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + resourceIdentifiers: + items: + description: Resource Customization fields for ignore difference + properties: + customization: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + group: + type: string + kind: + type: string + type: object + type: array + type: object + resourceInclusions: + description: ResourceInclusions is used to only include specific group/kinds + in the reconciliation process. + type: string + resourceTrackingMethod: + description: ResourceTrackingMethod defines how Argo CD should track + resources that it manages + type: string + server: + description: Server defines the options for the ArgoCD Server component. + properties: + autoscale: + description: Autoscale defines the autoscale options for the Argo + CD Server component. + properties: + enabled: + description: Enabled will toggle autoscaling support for the + Argo CD Server component. + type: boolean + hpa: + description: HPA defines the HorizontalPodAutoscaler options + for the Argo CD Server component. + properties: + maxReplicas: + description: upper limit for the number of pods that can + be set by the autoscaler; cannot be smaller than MinReplicas. + format: int32 + type: integer + minReplicas: + description: minReplicas is the lower limit for the number + of replicas to which the autoscaler can scale down. It + defaults to 1 pod. minReplicas is allowed to be 0 if + the alpha feature gate HPAScaleToZero is enabled and + at least one Object or External metric is configured. Scaling + is active as long as at least one metric value is available. + format: int32 + type: integer + scaleTargetRef: + description: reference to scaled resource; horizontal + pod autoscaler will learn the current resource consumption + and will set the desired number of pods by using its + Scale subresource. + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + required: + - kind + - name + type: object + targetCPUUtilizationPercentage: + description: target average CPU utilization (represented + as a percentage of requested CPU) over all the pods; + if not specified the default autoscaling policy will + be used. + format: int32 + type: integer + required: + - maxReplicas + - scaleTargetRef + type: object + required: + - enabled + type: object + env: + description: Env lets you specify environment for API server pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: Extra Command arguments that would append to the + Argo CD server command. ExtraCommandArgs will not be added, + if one of these commands is already part of the server command + with same or different value. + items: + type: string + type: array + grpc: + description: GRPC defines the state for the Argo CD Server GRPC + options. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for the Argo + CD Server GRPC Ingress. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + type: object + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + insecure: + description: Insecure toggles the insecure flag. + type: boolean + logFormat: + description: LogFormat refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogLevel if + not set. Valid options are debug, info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas for argocd-server. + Default is nil. Value should be greater than or equal to 0. + Value will be ignored if Autoscaler is enabled. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for the Argo CD server component. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + service: + description: Service defines the options for the Service backing + the ArgoCD Server component. + properties: + type: + description: Type is the ServiceType to use for the Service + resource. + type: string + required: + - type + type: object + type: object + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sso: + description: SSO defines the Single Sign-on configuration for Argo + CD + properties: + dex: + description: Dex contains the configuration for Argo CD dex authentication + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must + be a member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + image: + description: Image is the SSO container image. + type: string + keycloak: + description: Keycloak contains the configuration for Argo CD keycloak + authentication + properties: + image: + description: Image is the Keycloak container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Keycloak. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + rootCA: + description: Custom root CA certificate for communicating + with the Keycloak OIDC provider + type: string + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the Keycloak container image tag. + type: string + type: object + provider: + description: Provider installs and configures the given SSO Provider + with Argo CD. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for SSO. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the SSO container image tag. + type: string + type: object + statusBadgeEnabled: + description: StatusBadgeEnabled toggles application status badge feature. + type: boolean + tls: + description: TLS defines the TLS options for ArgoCD. + properties: + ca: + description: CA defines the CA options. + properties: + configMapName: + description: ConfigMapName is the name of the ConfigMap containing + the CA Certificate. + type: string + secretName: + description: SecretName is the name of the Secret containing + the CA Certificate and Key. + type: string + type: object + initialCerts: + additionalProperties: + type: string + description: InitialCerts defines custom TLS certificates upon + creation of the cluster for connecting Git repositories via + HTTPS. + type: object + type: object + usersAnonymousEnabled: + description: UsersAnonymousEnabled toggles anonymous user access. + The anonymous users get default role permissions specified argocd-rbac-cm. + type: boolean + version: + description: Version is the tag to use with the ArgoCD container image + for all ArgoCD components. + type: string + type: object + status: + description: ArgoCDStatus defines the observed state of ArgoCD + properties: + applicationController: + description: 'ApplicationController is a simple, high-level summary + of where the Argo CD application controller component is in its + lifecycle. There are four possible ApplicationController values: + Pending: The Argo CD application controller component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD application controller component are in a Ready state. Failed: + At least one of the Argo CD application controller component Pods + had a failure. Unknown: The state of the Argo CD application controller + component could not be obtained.' + type: string + applicationSetController: + description: 'ApplicationSetController is a simple, high-level summary + of where the Argo CD applicationSet controller component is in its + lifecycle. There are four possible ApplicationSetController values: + Pending: The Argo CD applicationSet controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD applicationSet controller component are in a Ready + state. Failed: At least one of the Argo CD applicationSet controller + component Pods had a failure. Unknown: The state of the Argo CD + applicationSet controller component could not be obtained.' + type: string + dex: + description: 'Dex is a simple, high-level summary of where the Argo + CD Dex component is in its lifecycle. There are four possible dex + values: Pending: The Argo CD Dex component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Dex component are in a Ready state. Failed: At least one + of the Argo CD Dex component Pods had a failure. Unknown: The state + of the Argo CD Dex component could not be obtained.' + type: string + host: + description: Host is the hostname of the Ingress. + type: string + notificationsController: + description: 'NotificationsController is a simple, high-level summary + of where the Argo CD notifications controller component is in its + lifecycle. There are four possible NotificationsController values: + Pending: The Argo CD notifications controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD notifications controller component are in a Ready + state. Failed: At least one of the Argo CD notifications controller + component Pods had a failure. Unknown: The state of the Argo CD + notifications controller component could not be obtained.' + type: string + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCD + is in its lifecycle. There are four possible phase values: Pending: + The ArgoCD has been accepted by the Kubernetes system, but one or + more of the required resources have not been created. Available: + All of the resources for the ArgoCD are ready. Failed: At least + one resource has experienced a failure. Unknown: The state of the + ArgoCD phase could not be obtained.' + type: string + redis: + description: 'Redis is a simple, high-level summary of where the Argo + CD Redis component is in its lifecycle. There are four possible + redis values: Pending: The Argo CD Redis component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Redis component are in a Ready state. Failed: At least one + of the Argo CD Redis component Pods had a failure. Unknown: The + state of the Argo CD Redis component could not be obtained.' + type: string + redisTLSChecksum: + description: RedisTLSChecksum contains the SHA256 checksum of the + latest known state of tls.crt and tls.key in the argocd-operator-redis-tls + secret. + type: string + repo: + description: 'Repo is a simple, high-level summary of where the Argo + CD Repo component is in its lifecycle. There are four possible repo + values: Pending: The Argo CD Repo component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Repo component are in a Ready state. Failed: At least one + of the Argo CD Repo component Pods had a failure. Unknown: The + state of the Argo CD Repo component could not be obtained.' + type: string + repoTLSChecksum: + description: RepoTLSChecksum contains the SHA256 checksum of the latest + known state of tls.crt and tls.key in the argocd-repo-server-tls + secret. + type: string + server: + description: 'Server is a simple, high-level summary of where the + Argo CD server component is in its lifecycle. There are four possible + server values: Pending: The Argo CD server component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD server component are in a Ready state. Failed: At least + one of the Argo CD server component Pods had a failure. Unknown: + The state of the Argo CD server component could not be obtained.' + type: string + ssoConfig: + description: 'SSOConfig defines the status of SSO configuration. Success: + Only one SSO provider is configured in CR. Failed: SSO configuration + is illegal or more than one SSO providers are configured in CR. + Unknown: The SSO configuration could not be obtained.' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/11_deployment_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/11_deployment_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..e643a9b820 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/11_deployment_argocd-operator-controller-manager.yaml @@ -0,0 +1,204 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system +spec: + replicas: 1 + revisionHistoryLimit: 1 + selector: + matchLabels: + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "AppProject", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Application", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ApplicationSet", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCD", + "metadata": { + "name": "argocd-sample" + }, + "spec": { + "controller": { + "resources": { + "limits": { + "cpu": "2000m", + "memory": "2048Mi" + }, + "requests": { + "cpu": "250m", + "memory": "1024Mi" + } + } + }, + "ha": { + "enabled": false, + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "redis": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "repo": { + "resources": { + "limits": { + "cpu": "1000m", + "memory": "512Mi" + }, + "requests": { + "cpu": "250m", + "memory": "256Mi" + } + } + }, + "server": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "125m", + "memory": "128Mi" + } + }, + "route": { + "enabled": true + } + }, + "sso": { + "dex": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "provider": "dex" + } + } + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCDExport", + "metadata": { + "name": "argocdexport-sample" + }, + "spec": { + "argocd": "argocd-sample" + } + } + ] + capabilities: Deep Insights + categories: Integration & Delivery + certified: "false" + containerImage: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + description: Argo CD is a declarative, GitOps continuous delivery tool for + Kubernetes. + olm.targetNamespaces: "" + operators.operatorframework.io/builder: operator-sdk-v1.10.0+git + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + repository: https://github.com/argoproj-labs/argocd-operator + support: Argo CD + labels: + control-plane: controller-manager + spec: + containers: + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + resources: {} + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + securityContext: + runAsNonRoot: true + serviceAccountName: argocd-operator-controller-manager + terminationGracePeriodSeconds: 10 +status: {} diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/12_service_argocd-operator-controller-manager-metrics-service.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/12_service_argocd-operator-controller-manager-metrics-service.yaml new file mode 100644 index 0000000000..e69c19f45a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/12_service_argocd-operator-controller-manager-metrics-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + control-plane: controller-manager + name: argocd-operator-controller-manager-metrics-service + namespace: argocd-system +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/13_serviceaccount_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/13_serviceaccount_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..8e5212c47c --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/all-namespaces/13_serviceaccount_argocd-operator-controller-manager.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml new file mode 100644 index 0000000000..19a68a5707 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: argocd-operator-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..d90e1d44b5 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,159 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - events + - namespaces + - persistentvolumeclaims + - pods + - secrets + - serviceaccounts + - services + - services/finalizers + verbs: + - '*' +- apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get +- apiGroups: + - apps + resources: + - daemonsets + - deployments + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - apps + resourceNames: + - argocd-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocdexports + - argocdexports/finalizers + - argocdexports/status + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocds + - argocds/finalizers + - argocds/status + verbs: + - '*' +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - '*' +- apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - '*' +- apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - list + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - prometheuses + - servicemonitors + verbs: + - '*' +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - '*' +- apiGroups: + - oauth.openshift.io + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - '*' +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - '*' +- apiGroups: + - template.openshift.io + resources: + - templateconfigs + - templateinstances + - templates + verbs: + - '*' +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..e1752b965a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/03_configmap_argocd-operator-manager-config.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/03_configmap_argocd-operator-manager-config.yaml new file mode 100644 index 0000000000..98d1f72c07 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/03_configmap_argocd-operator-manager-config.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +data: + controller_manager_config.yaml: | + apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 + kind: ControllerManagerConfig + health: + healthProbeBindAddress: :8081 + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + leaderElection: + leaderElect: true + resourceName: b674928d.argoproj.io +kind: ConfigMap +metadata: + name: argocd-operator-manager-config + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/04_customresourcedefinition_applications.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/04_customresourcedefinition_applications.argoproj.io.yaml new file mode 100644 index 0000000000..b1f398ec1b --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/04_customresourcedefinition_applications.argoproj.io.yaml @@ -0,0 +1,4018 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applications.argoproj.io + app.kubernetes.io/part-of: argocd + name: applications.argoproj.io +spec: + group: argoproj.io + names: + kind: Application + listKind: ApplicationList + plural: applications + shortNames: + - app + - apps + singular: application + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.sync.status + name: Sync Status + type: string + - jsonPath: .status.health.status + name: Health Status + type: string + - jsonPath: .status.sync.revision + name: Revision + priority: 10 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Application is a definition of Application resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + operation: + description: Operation contains information about a requested or running + operation + properties: + info: + description: Info is a list of informational items for this operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was initiated + automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who started + operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent retries + of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default unit + is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed + for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply --dry-run` + without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides sync + source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from the cluster + that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall be part + of the sync + items: + description: SyncOperationResource contains resources to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version (Helm) + which to sync the application to If omitted, will use the revision + specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or chart + version (Helm) which to sync each source in sources field for + the application to If omitted, will use the revision specified + in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by + not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources for + Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be + commit, tag, or branch. If omitted, will equal to HEAD. + In case of Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the sync + properties: + apply: + description: Apply will perform a `kubectl apply` to perform + the sync. + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources to + perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + type: object + type: object + type: object + spec: + description: ApplicationSpec represents desired application state. Contains + link to repository with application definition and additional parameters + link definition revision. + properties: + destination: + description: Destination is a reference to the target Kubernetes server + and namespace + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster and + must be set to the Kubernetes control plane API + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences is a list of resources and their fields + which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter + and list of json paths which should be ignored during comparison + with live state. + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + description: ManagedFieldsManagers is a list of trusted managers. + Fields mutated by those managers will take precedence over + the desired state defined in the SCM and won't be displayed + in diffs + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + description: Info contains a list of information (URLs, email addresses, + and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a reference to the project this application + belongs to. The empty string means that application belongs to the + 'default' project. + type: string + revisionHistoryLimit: + description: RevisionHistoryLimit limits the number of items kept + in the application's revision history, which is used for informational + purposes as well as for rollbacks to previous versions. This should + only be changed in exceptional circumstances. Setting to zero will + store no history. This will reduce storage used. Increasing will + increase the space used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location of the application's + manifests or chart + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being used + during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels to + add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to force + applying common annotations to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize to + use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the location of the application's + manifests or chart + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being + used during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to + force applying common annotations to resources for Kustomize + apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of + Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + description: SyncPolicy controls when and how a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the + target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources + (default: false)' + type: boolean + prune: + description: 'Prune specifies whether to delete resources + from the cluster that are not found in the sources anymore + as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal specifes whether to revert resources + back to their desired state upon modification in the cluster + (default: false)' + type: boolean + type: object + managedNamespaceMetadata: + description: ManagedNamespaceMetadata controls metadata in the + given namespace (if CreateNamespace=true) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time + allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + status: + description: ApplicationStatus contains status information for the application + properties: + conditions: + description: Conditions is a list of currently observed application + conditions + items: + description: ApplicationCondition contains details about an application + condition, which is usally an error or warning + properties: + lastTransitionTime: + description: LastTransitionTime is the time the condition was + last observed + format: date-time + type: string + message: + description: Message contains human-readable message indicating + details about condition + type: string + type: + description: Type is an application condition type + type: string + required: + - message + - type + type: object + type: array + health: + description: Health contains information about the application's current + health status + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application or + resource + type: string + type: object + history: + description: History contains information about the application's + sync history + items: + description: RevisionHistory contains history information about + a previous sync + properties: + deployStartedAt: + description: DeployStartedAt holds the time the sync operation + started + format: date-time + type: string + deployedAt: + description: DeployedAt holds the time the sync operation completed + format: date-time + type: string + id: + description: ID is an auto incrementing identifier of the RevisionHistory + format: int64 + type: integer + revision: + description: Revision holds the revision the sync was performed + against + type: string + revisions: + description: Revisions holds the revision of each source in + sources field the sync was performed against + items: + type: string + type: array + source: + description: Source is a reference to the application source + used for the sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application sources + used for the sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - deployedAt + - id + type: object + type: array + observedAt: + description: 'ObservedAt indicates when the application state was + updated without querying latest git state Deprecated: controller + no longer updates ObservedAt field' + format: date-time + type: string + operationState: + description: OperationState contains information about any ongoing + operations, such as a sync + properties: + finishedAt: + description: FinishedAt contains time of operation completion + format: date-time + type: string + message: + description: Message holds any pertinent messages when attempting + to perform operation (typically errors). + type: string + operation: + description: Operation is the original requested operation + properties: + info: + description: Info is a list of informational items for this + operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was + initiated automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who + started operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync + fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base + duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of + time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for + retrying a failed sync. If set to 0, no retries will + be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply + --dry-run` without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides + sync source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from + the cluster that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall + be part of the sync + items: + description: SyncOperationResource contains resources + to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version + (Helm) which to sync the application to If omitted, + will use the revision specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or + chart version (Helm) which to sync each source in sources + field for the application to If omitted, will use the + revision specified in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to + Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles do + not exist locally by not appending them to helm + template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of + Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in + the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + items: + description: ApplicationSource contains all required + information about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern + to match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern + to match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific + to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles + do not exist locally by not appending them + to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter + that's passed to helm template during manifest + generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release + name to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource + definition installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to + be passed to helm template, typically defined + as a block + type: string + version: + description: Version is the Helm version to + use for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific + options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of + additional annotations to add to rendered + manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended + to resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended + to resources for Kustomize apps + type: string + version: + description: Version controls which version + of Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the + Git repository, and is only valid for applications + sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry + in the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the + variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an + array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map + type parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a + string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source + within sources field. This field will not be used + if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision + of the source to sync the application to. In case + of Git, this can be commit, tag, or branch. If + omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, + e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the + sync + properties: + apply: + description: Apply will perform a `kubectl apply` + to perform the sync. + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources + to perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + type: object + type: object + type: object + phase: + description: Phase is the current phase of the operation + type: string + retryCount: + description: RetryCount contains time of operation retries + format: int64 + type: integer + startedAt: + description: StartedAt contains time of operation start + format: date-time + type: string + syncResult: + description: SyncResult is the result of a Sync operation + properties: + resources: + description: Resources contains a list of sync result items + for each individual resource in a sync operation + items: + description: ResourceResult holds the operation result details + of a specific resource + properties: + group: + description: Group specifies the API group of the resource + type: string + hookPhase: + description: HookPhase contains the state of any operation + associated with this resource OR hook This can also + contain values for non-hook resources. + type: string + hookType: + description: HookType specifies the type of the hook. + Empty for non-hook resources + type: string + kind: + description: Kind specifies the API kind of the resource + type: string + message: + description: Message contains an informational or error + message for the last sync OR operation + type: string + name: + description: Name specifies the name of the resource + type: string + namespace: + description: Namespace specifies the target namespace + of the resource + type: string + status: + description: Status holds the final result of the sync. + Will be empty if the resources is yet to be applied/pruned + and is always zero-value for hooks + type: string + syncPhase: + description: SyncPhase indicates the particular phase + of the sync that this result was acquired in + type: string + version: + description: Version specifies the API version of the + resource + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + revision: + description: Revision holds the revision this sync operation + was performed to + type: string + revisions: + description: Revisions holds the revision this sync operation + was performed for respective indexed source in sources field + items: + type: string + type: array + source: + description: Source records the application source information + of the sync, used for comparing auto-sync + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Source records the application source information + of the sync, used for comparing auto-sync + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - revision + type: object + required: + - operation + - phase + - startedAt + type: object + reconciledAt: + description: ReconciledAt indicates when the application state was + reconciled using the latest git version + format: date-time + type: string + resourceHealthSource: + description: 'ResourceHealthSource indicates where the resource health + status is stored: inline if not set or appTree' + type: string + resources: + description: Resources is a list of Kubernetes resources managed by + this application + items: + description: 'ResourceStatus holds the current sync and health status + of a resource TODO: describe members of this type' + properties: + group: + type: string + health: + description: HealthStatus contains information about the currently + observed health state of an application or resource + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application + or resource + type: string + type: object + hook: + type: boolean + kind: + type: string + name: + type: string + namespace: + type: string + requiresPruning: + type: boolean + status: + description: SyncStatusCode is a type which represents possible + comparison results + type: string + syncWave: + format: int64 + type: integer + version: + type: string + type: object + type: array + sourceType: + description: SourceType specifies the type of this application + type: string + sourceTypes: + description: SourceTypes specifies the type of the sources included + in the application + items: + description: ApplicationSourceType specifies the type of the application's + source + type: string + type: array + summary: + description: Summary contains a list of URLs and container images + used by this application + properties: + externalURLs: + description: ExternalURLs holds all external URLs of application + child resources. + items: + type: string + type: array + images: + description: Images holds all images of application child resources. + items: + type: string + type: array + type: object + sync: + description: Sync contains information about the application's current + sync status + properties: + comparedTo: + description: ComparedTo contains information about what has been + compared + properties: + destination: + description: Destination is a reference to the application's + destination used for comparison + properties: + name: + description: Name is an alternate way of specifying the + target cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace + for the application's resources. The namespace will + only be set for namespace-scoped resources that have + not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + source: + description: Source is a reference to the application's source + used for comparison + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application's multiple + sources used for comparison + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - destination + type: object + revision: + description: Revision contains information about the revision + the comparison has been performed to + type: string + revisions: + description: Revisions contains information about the revisions + of multiple sources the comparison has been performed to + items: + type: string + type: array + status: + description: Status is the sync state of the comparison + type: string + required: + - status + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml new file mode 100644 index 0000000000..272bd9e056 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml @@ -0,0 +1,10772 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applicationsets.argoproj.io + app.kubernetes.io/part-of: argocd + name: applicationsets.argoproj.io +spec: + group: argoproj.io + names: + kind: ApplicationSet + listKind: ApplicationSetList + plural: applicationsets + shortNames: + - appset + - appsets + singular: applicationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + type: object + merge: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + mergeKeys: + items: + type: string + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - mergeKeys + type: object + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + goTemplate: + type: boolean + strategy: + properties: + rollingSync: + properties: + steps: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + maxUpdate: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + type: object + type: + type: string + type: object + syncPolicy: + properties: + preserveResourcesOnDeletion: + type: boolean + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - template + type: object + status: + properties: + applicationStatus: + items: + properties: + application: + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + status: + type: string + step: + type: string + required: + - application + - message + - status + - step + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml new file mode 100644 index 0000000000..1ed93a159d --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml @@ -0,0 +1,328 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: appprojects.argoproj.io + app.kubernetes.io/part-of: argocd + name: appprojects.argoproj.io +spec: + group: argoproj.io + names: + kind: AppProject + listKind: AppProjectList + plural: appprojects + shortNames: + - appproj + - appprojs + singular: appproject + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'AppProject provides a logical grouping of applications, providing + controls for: * where the apps may deploy to (cluster whitelist) * what + may be deployed (repository whitelist, resource whitelist/blacklist) * who + can access these applications (roles, OIDC group claims bindings) * and + what they can do (RBAC policies) * automation access to these roles (JWT + tokens)' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppProjectSpec is the specification of an AppProject + properties: + clusterResourceBlacklist: + description: ClusterResourceBlacklist contains list of blacklisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + clusterResourceWhitelist: + description: ClusterResourceWhitelist contains list of whitelisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + description: + description: Description contains optional project description + type: string + destinations: + description: Destinations contains list of destinations available + for deployment + items: + description: ApplicationDestination holds information about the + application's destination + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + type: array + namespaceResourceBlacklist: + description: NamespaceResourceBlacklist contains list of blacklisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + namespaceResourceWhitelist: + description: NamespaceResourceWhitelist contains list of whitelisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + orphanedResources: + description: OrphanedResources specifies if controller should monitor + orphaned resources of apps in this project + properties: + ignore: + description: Ignore contains a list of resources that are to be + excluded from orphaned resources monitoring + items: + description: OrphanedResourceKey is a reference to a resource + to be ignored from + properties: + group: + type: string + kind: + type: string + name: + type: string + type: object + type: array + warn: + description: Warn indicates if warning condition should be created + for apps which have orphaned resources + type: boolean + type: object + permitOnlyProjectScopedClusters: + description: PermitOnlyProjectScopedClusters determines whether destinations + can only reference clusters which are project-scoped + type: boolean + roles: + description: Roles are user defined RBAC roles associated with this + project + items: + description: ProjectRole represents a role that has access to a + project + properties: + description: + description: Description is a description of the role + type: string + groups: + description: Groups are a list of OIDC group claims bound to + this role + items: + type: string + type: array + jwtTokens: + description: JWTTokens are a list of generated JWT tokens bound + to this role + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + name: + description: Name is a name for this role + type: string + policies: + description: Policies Stores a list of casbin formatted strings + that define access policies for the role in the project + items: + type: string + type: array + required: + - name + type: object + type: array + signatureKeys: + description: SignatureKeys contains a list of PGP key IDs that commits + in Git must be signed with in order to be allowed for sync + items: + description: SignatureKey is the specification of a key required + to verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sourceRepos: + description: SourceRepos contains list of repository URLs which can + be used for deployment + items: + type: string + type: array + syncWindows: + description: SyncWindows controls when syncs can be run for apps in + this project + items: + description: SyncWindow contains the kind, time, duration and attributes + that are used to assign the syncWindows to apps + properties: + applications: + description: Applications contains a list of applications that + the window will apply to + items: + type: string + type: array + clusters: + description: Clusters contains a list of clusters that the window + will apply to + items: + type: string + type: array + duration: + description: Duration is the amount of time the sync window + will be open + type: string + kind: + description: Kind defines if the window allows or blocks syncs + type: string + manualSync: + description: ManualSync enables manual syncs when they would + otherwise be blocked + type: boolean + namespaces: + description: Namespaces contains a list of namespaces that the + window will apply to + items: + type: string + type: array + schedule: + description: Schedule is the time the window will begin, specified + in cron format + type: string + timeZone: + description: TimeZone of the sync that will be applied to the + schedule + type: string + type: object + type: array + type: object + status: + description: AppProjectStatus contains status information for AppProject + CRs + properties: + jwtTokensByRole: + additionalProperties: + description: JWTTokens represents a list of JWT tokens + properties: + items: + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + type: object + description: JWTTokensByRole contains a list of JWT tokens issued + for a given role + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml new file mode 100644 index 0000000000..c3248d4740 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml @@ -0,0 +1,257 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocdexports.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCDExport + listKind: ArgoCDExportList + plural: argocdexports + singular: argocdexport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDExport is the Schema for the argocdexports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDExportSpec defines the desired state of ArgoCDExport + properties: + argocd: + description: Argocd is the name of the ArgoCD instance to export. + type: string + image: + description: Image is the container image to use for the export Job. + type: string + schedule: + description: Schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + type: string + storage: + description: Storage defines the storage configuration options. + properties: + backend: + description: Backend defines the storage backend to use, must + be "local" (the default), "aws", "azure" or "gcp". + type: string + pvc: + description: PVC is the desired characteristics for a PersistentVolumeClaim. + properties: + accessModes: + description: 'AccessModes contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify either: * + An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified data + source, it will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have the + same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which to populate + the volume with data, if a non-empty volume is desired. + This may be any local object from a non-empty API group + (non core object) or a PersistentVolumeClaim object. When + this field is specified, volume binding will only succeed + if the type of the specified object matches some installed + volume populator or dynamic provisioner. This field will + replace the functionality of the DataSource field and as + such if both fields are non-empty, they must have the same + value. For backwards compatibility, both fields (DataSource + and DataSourceRef) will be set to the same value automatically + if one of them is empty and the other is non-empty. There + are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed values (dropping + them), DataSourceRef preserves all values, and generates + an error if a disallowed value is specified. (Alpha) Using + this field requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum resources the + volume should have. If RecoverVolumeExpansionFailure feature + is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher + than capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + secretName: + description: SecretName is the name of a Secret with encryption + key, credentials, etc. + type: string + type: object + version: + description: Version is the tag/digest to use for the export Job container + image. + type: string + required: + - argocd + type: object + status: + description: ArgoCDExportStatus defines the observed state of ArgoCDExport + properties: + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCDExport + is in its lifecycle. There are five possible phase values: Pending: + The ArgoCDExport has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: + All of the containers for the ArgoCDExport are still running, or + in the process of starting or restarting. Succeeded: All containers + for the ArgoCDExport have terminated in success, and will not be + restarted. Failed: At least one container has terminated in failure, + either exited with non-zero status or was terminated by the system. + Unknown: For some reason the state of the ArgoCDExport could not + be obtained.' + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml new file mode 100644 index 0000000000..426a186d4e --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml @@ -0,0 +1,6443 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocds.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCD + listKind: ArgoCDList + plural: argocds + singular: argocd + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCD is the Schema for the argocds API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDSpec defines the desired state of ArgoCD + properties: + applicationInstanceLabelKey: + description: ApplicationInstanceLabelKey is the key name where Argo + CD injects the app name as a tracking label. + type: string + applicationSet: + description: ArgoCDApplicationSet defines whether the Argo CD ApplicationSet + controller should be installed. + properties: + env: + description: Env lets you specify environment for applicationSet + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: ExtraCommandArgs allows users to pass command line + arguments to ApplicationSet controller. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the Argo CD ApplicationSet image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the ApplicationSet controller. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for ApplicationSet. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD ApplicationSet image tag. + (optional) + type: string + webhookServer: + description: WebhookServerSpec defines the options for the ApplicationSet + Webhook Server component. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + use for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the + Route resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the + contents of the ca certificate of the final destination. When + using reencrypt termination this file should be + provided in order to have routers use it for health + checks on the secure connection. If this field is + not specified, the router may provide its own destination + CA and perform hostname validation using the short + service name (service.namespace.svc), which allows + infrastructure generated certificates to automatically + verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to + a route. While each router may make its own decisions + on which ports to expose, this is normally port + 80. \n * Allow - traffic is sent to the server on + the insecure port (default) * Disable - no traffic + is allowed on the insecure port. * Redirect - clients + are redirected to the secure port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + type: object + type: object + banner: + description: Banner defines an additional banner to be displayed in + Argo CD UI + properties: + content: + description: Content defines the banner message content to display + type: string + url: + description: URL defines an optional URL to be used as banner + message link + type: string + required: + - content + type: object + configManagementPlugins: + description: ConfigManagementPlugins is used to specify additional + config management plugins. + type: string + controller: + description: Controller defines the Application Controller options + for ArgoCD. + properties: + appSync: + description: "AppSync is used to control the sync frequency, by + default the ArgoCD controller polls Git every 3m. \n Set this + to a duration, e.g. 10m or 600s to control the synchronisation + frequency." + type: string + env: + description: Env lets you specify environment for application + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + logFormat: + description: LogFormat refers to the log format used by the Application + Controller component. Defaults to ArgoCDDefaultLogFormat if + not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level used by the Application + Controller component. Defaults to ArgoCDDefaultLogLevel if not + configured. Valid options are debug, info, error, and warn. + type: string + parallelismLimit: + description: ParallelismLimit defines the limit for parallel kubectl + operations + format: int32 + type: integer + processors: + description: Processors contains the options for the Application + Controller processors. + properties: + operation: + description: Operation is the number of application operation + processors. + format: int32 + type: integer + status: + description: Status is the number of application status processors. + format: int32 + type: integer + type: object + resources: + description: Resources defines the Compute Resources required + by the container for the Application Controller. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + sharding: + description: Sharding contains the options for the Application + Controller sharding configuration. + properties: + enabled: + description: Enabled defines whether sharding should be enabled + on the Application Controller component. + type: boolean + replicas: + description: Replicas defines the number of replicas to run + in the Application controller shard. + format: int32 + type: integer + type: object + type: object + dex: + description: Dex defines the Dex server options for ArgoCD. + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must be a + member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + disableAdmin: + description: DisableAdmin will disable the admin user. + type: boolean + extraConfig: + additionalProperties: + type: string + description: "ExtraConfig can be used to add fields to Argo CD configmap + that are not supported by Argo CD CRD. \n Note: ExtraConfig takes + precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin` + = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true. + In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"] + = true." + type: object + gaAnonymizeUsers: + description: GAAnonymizeUsers toggles user IDs being hashed before + sending to google analytics. + type: boolean + gaTrackingID: + description: GATrackingID is the google analytics tracking ID to use. + type: string + grafana: + description: Grafana defines the Grafana server options for ArgoCD. + properties: + enabled: + description: Enabled will toggle Grafana support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + image: + description: Image is the Grafana container image. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + resources: + description: Resources defines the Compute Resources required + by the container for Grafana. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Grafana Deployment. + format: int32 + type: integer + version: + description: Version is the Grafana container image tag. + type: string + required: + - enabled + type: object + ha: + description: HA options for High Availability support for the Redis + component. + properties: + enabled: + description: Enabled will toggle HA support globally for Argo + CD. + type: boolean + redisProxyImage: + description: RedisProxyImage is the Redis HAProxy container image. + type: string + redisProxyVersion: + description: RedisProxyVersion is the Redis HAProxy container + image tag. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for HA. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - enabled + type: object + helpChatText: + description: HelpChatText is the text for getting chat help, defaults + to "Chat now!" + type: string + helpChatURL: + description: HelpChatURL is the URL for getting chat help, this will + typically be your Slack channel for support. + type: string + image: + description: Image is the ArgoCD container image for all ArgoCD components. + type: string + import: + description: Import is the import/restore options for ArgoCD. + properties: + name: + description: Name of an ArgoCDExport from which to import data. + type: string + namespace: + description: Namespace for the ArgoCDExport, defaults to the same + namespace as the ArgoCD. + type: string + required: + - name + type: object + initialRepositories: + description: InitialRepositories to configure Argo CD with upon creation + of the cluster. + type: string + initialSSHKnownHosts: + description: InitialSSHKnownHosts defines the SSH known hosts data + upon creation of the cluster for connecting Git repositories via + SSH. + properties: + excludedefaulthosts: + description: ExcludeDefaultHosts describes whether you would like + to include the default list of SSH Known Hosts provided by ArgoCD. + type: boolean + keys: + description: Keys describes a custom set of SSH Known Hosts that + you would like to have included in your ArgoCD server. + type: string + type: object + kustomizeBuildOptions: + description: KustomizeBuildOptions is used to specify build options/parameters + to use with `kustomize build`. + type: string + kustomizeVersions: + description: KustomizeVersions is a listing of configured versions + of Kustomize to be made available within ArgoCD. + items: + description: KustomizeVersionSpec is used to specify information + about a kustomize version to be used within ArgoCD. + properties: + path: + description: Path is the path to a configured kustomize version + on the filesystem of your repo server. + type: string + version: + description: Version is a configured kustomize version in the + format of vX.Y.Z + type: string + type: object + type: array + monitoring: + description: Monitoring defines whether workload status monitoring + configuration for this instance. + properties: + enabled: + description: Enabled defines whether workload status monitoring + is enabled for this instance or not + type: boolean + required: + - enabled + type: object + nodePlacement: + description: NodePlacement defines NodeSelectors and Taints for Argo + CD workloads + properties: + nodeSelector: + additionalProperties: + type: string + description: NodeSelector is a field of PodSpec, it is a map of + key value pairs used for node selection + type: object + tolerations: + description: Tolerations allow the pods to schedule onto nodes + with matching taints + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + notifications: + description: Notifications defines whether the Argo CD Notifications + controller should be installed. + properties: + enabled: + description: Enabled defines whether argocd-notifications controller + should be deployed or not + type: boolean + env: + description: Env let you specify environment variables for Notifications + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is the Argo CD Notifications image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the argocd-notifications. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas to run for + notifications-controller + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Argo CD Notifications. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD Notifications image tag. (optional) + type: string + required: + - enabled + type: object + oidcConfig: + description: OIDCConfig is the OIDC configuration as an alternative + to dex. + type: string + prometheus: + description: Prometheus defines the Prometheus server options for + ArgoCD. + properties: + enabled: + description: Enabled will toggle Prometheus support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Prometheus StatefulSet. + format: int32 + type: integer + required: + - enabled + type: object + rbac: + description: RBAC defines the RBAC configuration for Argo CD. + properties: + defaultPolicy: + description: DefaultPolicy is the name of the default role which + Argo CD will falls back to, when authorizing API requests (optional). + If omitted or empty, users may be still be able to login, but + will see no apps, projects, etc... + type: string + policy: + description: 'Policy is CSV containing user-defined RBAC policies + and role definitions. Policy rules are in the form: p, subject, + resource, action, object, effect Role definitions and bindings + are in the form: g, subject, inherited-subject See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md + for additional information.' + type: string + policyMatcherMode: + description: PolicyMatcherMode configures the matchers function + mode for casbin. There are two options for this, 'glob' for + glob matcher or 'regex' for regex matcher. + type: string + scopes: + description: 'Scopes controls which OIDC scopes to examine during + rbac enforcement (in addition to `sub` scope). If omitted, defaults + to: ''[groups]''.' + type: string + type: object + redis: + description: Redis defines the Redis server options for ArgoCD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the redis server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + disableTLSVerification: + description: DisableTLSVerification defines whether redis server + API should be accessed using strict TLS validation + type: boolean + image: + description: Image is the Redis container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Redis container image tag. + type: string + type: object + repo: + description: Repo defines the repo server options for Argo CD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the repo server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + env: + description: Env lets you specify environment for repo server + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + execTimeout: + description: ExecTimeout specifies the timeout in seconds for + tool execution + type: integer + extraRepoCommandArgs: + description: Extra Command arguments allows users to pass command + line arguments to repo server workload. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraRepoCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the ArgoCD Repo Server container image. + type: string + initContainers: + description: InitContainers defines the list of initialization + containers for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + logFormat: + description: LogFormat describes the log format that should be + used by the Repo Server. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the Repo Server. Defaults to ArgoCDDefaultLogLevel if not + set. Valid options are debug, info, error, and warn. + type: string + mountsatoken: + description: MountSAToken describes whether you would like to + have the Repo server mount the service account token + type: boolean + replicas: + description: Replicas defines the number of replicas for argocd-repo-server. + Value should be greater than or equal to 0. Default is nil. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + serviceaccount: + description: ServiceAccount defines the ServiceAccount user that + you would like the Repo server to use + type: string + sidecarContainers: + description: SidecarContainers defines the list of sidecar containers + for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + verifytls: + description: VerifyTLS defines whether repo server API should + be accessed using strict TLS validation + type: boolean + version: + description: Version is the ArgoCD Repo Server container image + tag. + type: string + volumeMounts: + description: VolumeMounts adds volumeMounts to the repo server + container + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: Volumes adds volumes to the repo server deployment + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob + disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure + Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of + Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the ConfigMap, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed + to the associated CSI driver which will determine + the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all + containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "Ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is + tied to the pod that defines it - it will be created before + the pod starts, and deleted when the pod is removed. \n + Use this if: a) the volume is only needed while the pod + runs, b) features of normal volumes like restoring from + snapshot or capacity tracking are needed, c) the storage + driver is specified through a storage class, and d) the + storage driver supports dynamic volume provisioning through + \ a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'AccessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify + either: * An existing VolumeSnapshot object + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) If + the provisioner or an external controller + can support the specified data source, it + will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always + have the same contents as the DataSourceRef + field.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which + to populate the volume with data, if a non-empty + volume is desired. This may be any local object + from a non-empty API group (non core object) + or a PersistentVolumeClaim object. When this + field is specified, volume binding will only + succeed if the type of the specified object + matches some installed volume populator or + dynamic provisioner. This field will replace + the functionality of the DataSource field + and as such if both fields are non-empty, + they must have the same value. For backwards + compatibility, both fields (DataSource and + DataSourceRef) will be set to the same value + automatically if one of them is empty and + the other is non-empty. There are two important + differences between DataSource and DataSourceRef: + * While DataSource only allows two specific + types of objects, DataSourceRef allows any + non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed + values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed + value is specified. (Alpha) Using this field + requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum + resources the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify + resource requirements that are lower than + previous value but must still be higher than + capacity recorded in the status field of the + claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: VolumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: FC represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use + for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to + pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the + plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. + Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the volume + directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'Path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will + be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI + transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is + either an IP or ip_addr:port if the port is other + than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits used to set permissions on created + files by default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. Directories within the + path are not affected by this setting. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in + the ScaleIO system that is associated with this volume + source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start + with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys + must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + repositoryCredentials: + description: RepositoryCredentials are the Git pull credentials to + configure Argo CD with upon creation of the cluster. + type: string + resourceActions: + description: ResourceActions customizes resource action behavior. + items: + description: Resource Customization for custom action + properties: + action: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceCustomizations: + description: 'ResourceCustomizations customizes resource behavior. + Keys are in the form: group/Kind. Please note that this is being + deprecated in favor of ResourceHealthChecks, ResourceIgnoreDifferences, + and ResourceActions.' + type: string + resourceExclusions: + description: ResourceExclusions is used to completely ignore entire + classes of resource group/kinds. + type: string + resourceHealthChecks: + description: ResourceHealthChecks customizes resource health check + behavior. + items: + description: Resource Customization for custom health check + properties: + check: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceIgnoreDifferences: + description: ResourceIgnoreDifferences customizes resource ignore + difference behavior. + properties: + all: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + resourceIdentifiers: + items: + description: Resource Customization fields for ignore difference + properties: + customization: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + group: + type: string + kind: + type: string + type: object + type: array + type: object + resourceInclusions: + description: ResourceInclusions is used to only include specific group/kinds + in the reconciliation process. + type: string + resourceTrackingMethod: + description: ResourceTrackingMethod defines how Argo CD should track + resources that it manages + type: string + server: + description: Server defines the options for the ArgoCD Server component. + properties: + autoscale: + description: Autoscale defines the autoscale options for the Argo + CD Server component. + properties: + enabled: + description: Enabled will toggle autoscaling support for the + Argo CD Server component. + type: boolean + hpa: + description: HPA defines the HorizontalPodAutoscaler options + for the Argo CD Server component. + properties: + maxReplicas: + description: upper limit for the number of pods that can + be set by the autoscaler; cannot be smaller than MinReplicas. + format: int32 + type: integer + minReplicas: + description: minReplicas is the lower limit for the number + of replicas to which the autoscaler can scale down. It + defaults to 1 pod. minReplicas is allowed to be 0 if + the alpha feature gate HPAScaleToZero is enabled and + at least one Object or External metric is configured. Scaling + is active as long as at least one metric value is available. + format: int32 + type: integer + scaleTargetRef: + description: reference to scaled resource; horizontal + pod autoscaler will learn the current resource consumption + and will set the desired number of pods by using its + Scale subresource. + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + required: + - kind + - name + type: object + targetCPUUtilizationPercentage: + description: target average CPU utilization (represented + as a percentage of requested CPU) over all the pods; + if not specified the default autoscaling policy will + be used. + format: int32 + type: integer + required: + - maxReplicas + - scaleTargetRef + type: object + required: + - enabled + type: object + env: + description: Env lets you specify environment for API server pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: Extra Command arguments that would append to the + Argo CD server command. ExtraCommandArgs will not be added, + if one of these commands is already part of the server command + with same or different value. + items: + type: string + type: array + grpc: + description: GRPC defines the state for the Argo CD Server GRPC + options. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for the Argo + CD Server GRPC Ingress. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + type: object + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + insecure: + description: Insecure toggles the insecure flag. + type: boolean + logFormat: + description: LogFormat refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogLevel if + not set. Valid options are debug, info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas for argocd-server. + Default is nil. Value should be greater than or equal to 0. + Value will be ignored if Autoscaler is enabled. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for the Argo CD server component. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + service: + description: Service defines the options for the Service backing + the ArgoCD Server component. + properties: + type: + description: Type is the ServiceType to use for the Service + resource. + type: string + required: + - type + type: object + type: object + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sso: + description: SSO defines the Single Sign-on configuration for Argo + CD + properties: + dex: + description: Dex contains the configuration for Argo CD dex authentication + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must + be a member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + image: + description: Image is the SSO container image. + type: string + keycloak: + description: Keycloak contains the configuration for Argo CD keycloak + authentication + properties: + image: + description: Image is the Keycloak container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Keycloak. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + rootCA: + description: Custom root CA certificate for communicating + with the Keycloak OIDC provider + type: string + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the Keycloak container image tag. + type: string + type: object + provider: + description: Provider installs and configures the given SSO Provider + with Argo CD. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for SSO. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the SSO container image tag. + type: string + type: object + statusBadgeEnabled: + description: StatusBadgeEnabled toggles application status badge feature. + type: boolean + tls: + description: TLS defines the TLS options for ArgoCD. + properties: + ca: + description: CA defines the CA options. + properties: + configMapName: + description: ConfigMapName is the name of the ConfigMap containing + the CA Certificate. + type: string + secretName: + description: SecretName is the name of the Secret containing + the CA Certificate and Key. + type: string + type: object + initialCerts: + additionalProperties: + type: string + description: InitialCerts defines custom TLS certificates upon + creation of the cluster for connecting Git repositories via + HTTPS. + type: object + type: object + usersAnonymousEnabled: + description: UsersAnonymousEnabled toggles anonymous user access. + The anonymous users get default role permissions specified argocd-rbac-cm. + type: boolean + version: + description: Version is the tag to use with the ArgoCD container image + for all ArgoCD components. + type: string + type: object + status: + description: ArgoCDStatus defines the observed state of ArgoCD + properties: + applicationController: + description: 'ApplicationController is a simple, high-level summary + of where the Argo CD application controller component is in its + lifecycle. There are four possible ApplicationController values: + Pending: The Argo CD application controller component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD application controller component are in a Ready state. Failed: + At least one of the Argo CD application controller component Pods + had a failure. Unknown: The state of the Argo CD application controller + component could not be obtained.' + type: string + applicationSetController: + description: 'ApplicationSetController is a simple, high-level summary + of where the Argo CD applicationSet controller component is in its + lifecycle. There are four possible ApplicationSetController values: + Pending: The Argo CD applicationSet controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD applicationSet controller component are in a Ready + state. Failed: At least one of the Argo CD applicationSet controller + component Pods had a failure. Unknown: The state of the Argo CD + applicationSet controller component could not be obtained.' + type: string + dex: + description: 'Dex is a simple, high-level summary of where the Argo + CD Dex component is in its lifecycle. There are four possible dex + values: Pending: The Argo CD Dex component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Dex component are in a Ready state. Failed: At least one + of the Argo CD Dex component Pods had a failure. Unknown: The state + of the Argo CD Dex component could not be obtained.' + type: string + host: + description: Host is the hostname of the Ingress. + type: string + notificationsController: + description: 'NotificationsController is a simple, high-level summary + of where the Argo CD notifications controller component is in its + lifecycle. There are four possible NotificationsController values: + Pending: The Argo CD notifications controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD notifications controller component are in a Ready + state. Failed: At least one of the Argo CD notifications controller + component Pods had a failure. Unknown: The state of the Argo CD + notifications controller component could not be obtained.' + type: string + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCD + is in its lifecycle. There are four possible phase values: Pending: + The ArgoCD has been accepted by the Kubernetes system, but one or + more of the required resources have not been created. Available: + All of the resources for the ArgoCD are ready. Failed: At least + one resource has experienced a failure. Unknown: The state of the + ArgoCD phase could not be obtained.' + type: string + redis: + description: 'Redis is a simple, high-level summary of where the Argo + CD Redis component is in its lifecycle. There are four possible + redis values: Pending: The Argo CD Redis component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Redis component are in a Ready state. Failed: At least one + of the Argo CD Redis component Pods had a failure. Unknown: The + state of the Argo CD Redis component could not be obtained.' + type: string + redisTLSChecksum: + description: RedisTLSChecksum contains the SHA256 checksum of the + latest known state of tls.crt and tls.key in the argocd-operator-redis-tls + secret. + type: string + repo: + description: 'Repo is a simple, high-level summary of where the Argo + CD Repo component is in its lifecycle. There are four possible repo + values: Pending: The Argo CD Repo component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Repo component are in a Ready state. Failed: At least one + of the Argo CD Repo component Pods had a failure. Unknown: The + state of the Argo CD Repo component could not be obtained.' + type: string + repoTLSChecksum: + description: RepoTLSChecksum contains the SHA256 checksum of the latest + known state of tls.crt and tls.key in the argocd-repo-server-tls + secret. + type: string + server: + description: 'Server is a simple, high-level summary of where the + Argo CD server component is in its lifecycle. There are four possible + server values: Pending: The Argo CD server component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD server component are in a Ready state. Failed: At least + one of the Argo CD server component Pods had a failure. Unknown: + The state of the Argo CD server component could not be obtained.' + type: string + ssoConfig: + description: 'SSOConfig defines the status of SSO configuration. Success: + Only one SSO provider is configured in CR. Failed: SSO configuration + is illegal or more than one SSO providers are configured in CR. + Unknown: The SSO configuration could not be obtained.' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/09_deployment_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/09_deployment_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..f07080f3f3 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/09_deployment_argocd-operator-controller-manager.yaml @@ -0,0 +1,204 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system +spec: + replicas: 1 + revisionHistoryLimit: 1 + selector: + matchLabels: + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "AppProject", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Application", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ApplicationSet", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCD", + "metadata": { + "name": "argocd-sample" + }, + "spec": { + "controller": { + "resources": { + "limits": { + "cpu": "2000m", + "memory": "2048Mi" + }, + "requests": { + "cpu": "250m", + "memory": "1024Mi" + } + } + }, + "ha": { + "enabled": false, + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "redis": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "repo": { + "resources": { + "limits": { + "cpu": "1000m", + "memory": "512Mi" + }, + "requests": { + "cpu": "250m", + "memory": "256Mi" + } + } + }, + "server": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "125m", + "memory": "128Mi" + } + }, + "route": { + "enabled": true + } + }, + "sso": { + "dex": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "provider": "dex" + } + } + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCDExport", + "metadata": { + "name": "argocdexport-sample" + }, + "spec": { + "argocd": "argocd-sample" + } + } + ] + capabilities: Deep Insights + categories: Integration & Delivery + certified: "false" + containerImage: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + description: Argo CD is a declarative, GitOps continuous delivery tool for + Kubernetes. + olm.targetNamespaces: argocd-system + operators.operatorframework.io/builder: operator-sdk-v1.10.0+git + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + repository: https://github.com/argoproj-labs/argocd-operator + support: Argo CD + labels: + control-plane: controller-manager + spec: + containers: + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + resources: {} + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + securityContext: + runAsNonRoot: true + serviceAccountName: argocd-operator-controller-manager + terminationGracePeriodSeconds: 10 +status: {} diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml new file mode 100644 index 0000000000..fd60a6dc8c --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml @@ -0,0 +1,37 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + namespace: argocd-system +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml new file mode 100644 index 0000000000..c3a6e24e8a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + namespace: argocd-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml new file mode 100644 index 0000000000..e69c19f45a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + control-plane: controller-manager + name: argocd-operator-controller-manager-metrics-service + namespace: argocd-system +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..8e5212c47c --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/own-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml new file mode 100644 index 0000000000..19a68a5707 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/00_clusterrole_argocd-operator-metrics-reader.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: argocd-operator-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..d90e1d44b5 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/01_clusterrole_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,159 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - events + - namespaces + - persistentvolumeclaims + - pods + - secrets + - serviceaccounts + - services + - services/finalizers + verbs: + - '*' +- apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get +- apiGroups: + - apps + resources: + - daemonsets + - deployments + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - apps + resourceNames: + - argocd-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - applications + - appprojects + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocdexports + - argocdexports/finalizers + - argocdexports/status + verbs: + - '*' +- apiGroups: + - argoproj.io + resources: + - argocds + - argocds/finalizers + - argocds/status + verbs: + - '*' +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - '*' +- apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - '*' +- apiGroups: + - config.openshift.io + resources: + - clusterversions + verbs: + - get + - list + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - prometheuses + - servicemonitors + verbs: + - '*' +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - '*' +- apiGroups: + - oauth.openshift.io + resources: + - oauthclients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - '*' +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - '*' +- apiGroups: + - template.openshift.io + resources: + - templateconfigs + - templateinstances + - templates + verbs: + - '*' +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml new file mode 100644 index 0000000000..e1752b965a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/02_clusterrolebinding_argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/03_configmap_argocd-operator-manager-config.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/03_configmap_argocd-operator-manager-config.yaml new file mode 100644 index 0000000000..98d1f72c07 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/03_configmap_argocd-operator-manager-config.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +data: + controller_manager_config.yaml: | + apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 + kind: ControllerManagerConfig + health: + healthProbeBindAddress: :8081 + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + leaderElection: + leaderElect: true + resourceName: b674928d.argoproj.io +kind: ConfigMap +metadata: + name: argocd-operator-manager-config + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/04_customresourcedefinition_applications.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/04_customresourcedefinition_applications.argoproj.io.yaml new file mode 100644 index 0000000000..b1f398ec1b --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/04_customresourcedefinition_applications.argoproj.io.yaml @@ -0,0 +1,4018 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applications.argoproj.io + app.kubernetes.io/part-of: argocd + name: applications.argoproj.io +spec: + group: argoproj.io + names: + kind: Application + listKind: ApplicationList + plural: applications + shortNames: + - app + - apps + singular: application + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.sync.status + name: Sync Status + type: string + - jsonPath: .status.health.status + name: Health Status + type: string + - jsonPath: .status.sync.revision + name: Revision + priority: 10 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Application is a definition of Application resource. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + operation: + description: Operation contains information about a requested or running + operation + properties: + info: + description: Info is a list of informational items for this operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was initiated + automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who started + operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent retries + of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default unit + is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed + for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply --dry-run` + without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides sync + source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from the cluster + that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall be part + of the sync + items: + description: SyncOperationResource contains resources to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version (Helm) + which to sync the application to If omitted, will use the revision + specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or chart + version (Helm) which to sync each source in sources field for + the application to If omitted, will use the revision specified + in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to + be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by + not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources for + Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be + commit, tag, or branch. If omitted, will equal to HEAD. + In case of Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set in the + application. This is typically set in a Rollback operation and + is nil during a Sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the sync + properties: + apply: + description: Apply will perform a `kubectl apply` to perform + the sync. + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources to + perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to supply + the --force flag to `kubectl apply`. The --force flag + deletes and re-create the resource, when PATCH encounters + conflict and has retried for 5 times. + type: boolean + type: object + type: object + type: object + type: object + spec: + description: ApplicationSpec represents desired application state. Contains + link to repository with application definition and additional parameters + link definition revision. + properties: + destination: + description: Destination is a reference to the target Kubernetes server + and namespace + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster and + must be set to the Kubernetes control plane API + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences is a list of resources and their fields + which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter + and list of json paths which should be ignored during comparison + with live state. + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + description: ManagedFieldsManagers is a list of trusted managers. + Fields mutated by those managers will take precedence over + the desired state defined in the SCM and won't be displayed + in diffs + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + description: Info contains a list of information (URLs, email addresses, + and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a reference to the project this application + belongs to. The empty string means that application belongs to the + 'default' project. + type: string + revisionHistoryLimit: + description: RevisionHistoryLimit limits the number of items kept + in the application's revision history, which is used for informational + purposes as well as for rollbacks to previous versions. This should + only be changed in exceptional circumstances. Setting to zero will + store no history. This will reduce storage used. Increasing will + increase the space used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location of the application's + manifests or chart + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being used + during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels to + add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to force + applying common annotations to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize to + use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the location of the application's + manifests or chart + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match paths + against that should be explicitly excluded from being + used during manifest generation + type: string + include: + description: Include contains a glob pattern to match paths + against that should be explicitly included during manifest + generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar represents a variable to be + passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm + template + items: + description: HelmFileParameter is a file parameter that's + passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally by not + appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters which + are passed to the helm template command upon manifest + generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to tell + Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all domains + (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to use. + If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition installation + step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files to + use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed to + helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating + ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional annotations + to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether to + force applying common annotations to resources for Kustomize + apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to force + applying common labels to resources for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize image + definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, usually + expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string type + parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or Helm) + that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the source + to sync the application to. In case of Git, this can be commit, + tag, or branch. If omitted, will equal to HEAD. In case of + Helm, this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + description: SyncPolicy controls when and how a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the + target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources + (default: false)' + type: boolean + prune: + description: 'Prune specifies whether to delete resources + from the cluster that are not found in the sources anymore + as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal specifes whether to revert resources + back to their desired state upon modification in the cluster + (default: false)' + type: boolean + type: object + managedNamespaceMetadata: + description: ManagedNamespaceMetadata controls metadata in the + given namespace (if CreateNamespace=true) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration + after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time + allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for retrying + a failed sync. If set to 0, no retries will be performed. + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + status: + description: ApplicationStatus contains status information for the application + properties: + conditions: + description: Conditions is a list of currently observed application + conditions + items: + description: ApplicationCondition contains details about an application + condition, which is usally an error or warning + properties: + lastTransitionTime: + description: LastTransitionTime is the time the condition was + last observed + format: date-time + type: string + message: + description: Message contains human-readable message indicating + details about condition + type: string + type: + description: Type is an application condition type + type: string + required: + - message + - type + type: object + type: array + health: + description: Health contains information about the application's current + health status + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application or + resource + type: string + type: object + history: + description: History contains information about the application's + sync history + items: + description: RevisionHistory contains history information about + a previous sync + properties: + deployStartedAt: + description: DeployStartedAt holds the time the sync operation + started + format: date-time + type: string + deployedAt: + description: DeployedAt holds the time the sync operation completed + format: date-time + type: string + id: + description: ID is an auto incrementing identifier of the RevisionHistory + format: int64 + type: integer + revision: + description: Revision holds the revision the sync was performed + against + type: string + revisions: + description: Revisions holds the revision of each source in + sources field the sync was performed against + items: + type: string + type: array + source: + description: Source is a reference to the application source + used for the sync operation + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded from + being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included during + manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the + helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm template + from failing when valueFiles do not exist locally + by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's passed + to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether to + tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name to + use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional labels + to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable entries + items: + description: EnvEntry represents an entry in the application's + environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array type + parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type parameter. + type: object + name: + description: Name is the name identifying a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within sources + field. This field will not be used if used with a `source` + tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application sources + used for the sync operation + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - deployedAt + - id + type: object + type: array + observedAt: + description: 'ObservedAt indicates when the application state was + updated without querying latest git state Deprecated: controller + no longer updates ObservedAt field' + format: date-time + type: string + operationState: + description: OperationState contains information about any ongoing + operations, such as a sync + properties: + finishedAt: + description: FinishedAt contains time of operation completion + format: date-time + type: string + message: + description: Message holds any pertinent messages when attempting + to perform operation (typically errors). + type: string + operation: + description: Operation is the original requested operation + properties: + info: + description: Info is a list of informational items for this + operation + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + initiatedBy: + description: InitiatedBy contains information about who initiated + the operations + properties: + automated: + description: Automated is set to true if operation was + initiated automatically by the application controller. + type: boolean + username: + description: Username contains the name of a user who + started operation + type: string + type: object + retry: + description: Retry controls the strategy to apply if a sync + fails + properties: + backoff: + description: Backoff controls how to backoff on subsequent + retries of failed syncs + properties: + duration: + description: Duration is the amount to back off. Default + unit is seconds, but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base + duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of + time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts for + retrying a failed sync. If set to 0, no retries will + be performed. + format: int64 + type: integer + type: object + sync: + description: Sync contains parameters for the operation + properties: + dryRun: + description: DryRun specifies to perform a `kubectl apply + --dry-run` without actually performing the sync + type: boolean + manifests: + description: Manifests is an optional field that overrides + sync source with a local directory for development + items: + type: string + type: array + prune: + description: Prune specifies to delete resources from + the cluster that are no longer tracked in git + type: boolean + resources: + description: Resources describes which resources shall + be part of the sync + items: + description: SyncOperationResource contains resources + to sync. + properties: + group: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + type: array + revision: + description: Revision is the revision (Git) or chart version + (Helm) which to sync the application to If omitted, + will use the revision specified in app spec. + type: string + revisions: + description: Revisions is the list of revision (Git) or + chart version (Helm) which to sync each source in sources + field for the application to If omitted, will use the + revision specified in app spec. + items: + type: string + type: array + source: + description: Source overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to + Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles do + not exist locally by not appending them to helm + template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of + Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in + the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources overrides the source definition set + in the application. This is typically set in a Rollback + operation and is nil during a Sync operation + items: + description: ApplicationSource contains all required + information about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must + be specified for applications sourced from a Helm + repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern + to match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern + to match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific + to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan + a directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents + helm template from failing when valueFiles + do not exist locally by not appending them + to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter + that's passed to helm template during manifest + generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and + numbers as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the + Helm parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials + to all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release + name to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource + definition installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to + be passed to helm template, typically defined + as a block + type: string + version: + description: Version is the Helm version to + use for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific + options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of + additional annotations to add to rendered + manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies + whether to force applying common annotations + to resources for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources + for Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended + to resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended + to resources for Kustomize apps + type: string + version: + description: Version controls which version + of Kustomize to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the + Git repository, and is only valid for applications + sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin + specific options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry + in the application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the + variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an + array type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map + type parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a + string type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source + within sources field. This field will not be used + if used with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository + (Git or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision + of the source to sync the application to. In case + of Git, this can be commit, tag, or branch. If + omitted, will equal to HEAD. In case of Helm, + this is a semver tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + syncOptions: + description: SyncOptions provide per-sync sync-options, + e.g. Validate=false + items: + type: string + type: array + syncStrategy: + description: SyncStrategy describes how to perform the + sync + properties: + apply: + description: Apply will perform a `kubectl apply` + to perform the sync. + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + hook: + description: Hook will submit any referenced resources + to perform the sync. This is the default strategy + properties: + force: + description: Force indicates whether or not to + supply the --force flag to `kubectl apply`. + The --force flag deletes and re-create the resource, + when PATCH encounters conflict and has retried + for 5 times. + type: boolean + type: object + type: object + type: object + type: object + phase: + description: Phase is the current phase of the operation + type: string + retryCount: + description: RetryCount contains time of operation retries + format: int64 + type: integer + startedAt: + description: StartedAt contains time of operation start + format: date-time + type: string + syncResult: + description: SyncResult is the result of a Sync operation + properties: + resources: + description: Resources contains a list of sync result items + for each individual resource in a sync operation + items: + description: ResourceResult holds the operation result details + of a specific resource + properties: + group: + description: Group specifies the API group of the resource + type: string + hookPhase: + description: HookPhase contains the state of any operation + associated with this resource OR hook This can also + contain values for non-hook resources. + type: string + hookType: + description: HookType specifies the type of the hook. + Empty for non-hook resources + type: string + kind: + description: Kind specifies the API kind of the resource + type: string + message: + description: Message contains an informational or error + message for the last sync OR operation + type: string + name: + description: Name specifies the name of the resource + type: string + namespace: + description: Namespace specifies the target namespace + of the resource + type: string + status: + description: Status holds the final result of the sync. + Will be empty if the resources is yet to be applied/pruned + and is always zero-value for hooks + type: string + syncPhase: + description: SyncPhase indicates the particular phase + of the sync that this result was acquired in + type: string + version: + description: Version specifies the API version of the + resource + type: string + required: + - group + - kind + - name + - namespace + - version + type: object + type: array + revision: + description: Revision holds the revision this sync operation + was performed to + type: string + revisions: + description: Revisions holds the revision this sync operation + was performed for respective indexed source in sources field + items: + type: string + type: array + source: + description: Source records the application source information + of the sync, used for comparing auto-sync + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Source records the application source information + of the sync, used for comparing auto-sync + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - revision + type: object + required: + - operation + - phase + - startedAt + type: object + reconciledAt: + description: ReconciledAt indicates when the application state was + reconciled using the latest git version + format: date-time + type: string + resourceHealthSource: + description: 'ResourceHealthSource indicates where the resource health + status is stored: inline if not set or appTree' + type: string + resources: + description: Resources is a list of Kubernetes resources managed by + this application + items: + description: 'ResourceStatus holds the current sync and health status + of a resource TODO: describe members of this type' + properties: + group: + type: string + health: + description: HealthStatus contains information about the currently + observed health state of an application or resource + properties: + message: + description: Message is a human-readable informational message + describing the health status + type: string + status: + description: Status holds the status code of the application + or resource + type: string + type: object + hook: + type: boolean + kind: + type: string + name: + type: string + namespace: + type: string + requiresPruning: + type: boolean + status: + description: SyncStatusCode is a type which represents possible + comparison results + type: string + syncWave: + format: int64 + type: integer + version: + type: string + type: object + type: array + sourceType: + description: SourceType specifies the type of this application + type: string + sourceTypes: + description: SourceTypes specifies the type of the sources included + in the application + items: + description: ApplicationSourceType specifies the type of the application's + source + type: string + type: array + summary: + description: Summary contains a list of URLs and container images + used by this application + properties: + externalURLs: + description: ExternalURLs holds all external URLs of application + child resources. + items: + type: string + type: array + images: + description: Images holds all images of application child resources. + items: + type: string + type: array + type: object + sync: + description: Sync contains information about the application's current + sync status + properties: + comparedTo: + description: ComparedTo contains information about what has been + compared + properties: + destination: + description: Destination is a reference to the application's + destination used for comparison + properties: + name: + description: Name is an alternate way of specifying the + target cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace + for the application's resources. The namespace will + only be set for namespace-scoped resources that have + not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + source: + description: Source is a reference to the application's source + used for comparison + properties: + chart: + description: Chart is a Helm chart name, and must be specified + for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + description: Exclude contains a glob pattern to match + paths against that should be explicitly excluded + from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to match + paths against that should be explicitly included + during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a directory + recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to + the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm parameter + type: string + path: + description: Path is the path to the file containing + the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command upon + manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to all + domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value files + to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be passed + to helm template, typically defined as a block + type: string + version: + description: Version is the Helm version to use for + templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether to + force applying common labels to resources for Kustomize + apps + type: boolean + images: + description: Images is a list of Kustomize image override + specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources + for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources + for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying a + parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used with + a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - repoURL + type: object + sources: + description: Sources is a reference to the application's multiple + sources used for comparison + items: + description: ApplicationSource contains all required information + about the source of an application + properties: + chart: + description: Chart is a Helm chart name, and must be + specified for applications sourced from a Helm repo. + type: string + directory: + description: Directory holds path/directory specific + options + properties: + exclude: + description: Exclude contains a glob pattern to + match paths against that should be explicitly + excluded from being used during manifest generation + type: string + include: + description: Include contains a glob pattern to + match paths against that should be explicitly + included during manifest generation + type: string + jsonnet: + description: Jsonnet holds options specific to Jsonnet + properties: + extVars: + description: ExtVars is a list of Jsonnet External + Variables + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level + Arguments + items: + description: JsonnetVar represents a variable + to be passed to jsonnet during manifest + generation + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + description: Recurse specifies whether to scan a + directory recursively for manifests + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file parameter + that's passed to helm template during manifest + generation + properties: + name: + description: Name is the name of the Helm + parameter + type: string + path: + description: Path is the path to the file + containing the values for the Helm parameter + type: string + type: object + type: array + ignoreMissingValueFiles: + description: IgnoreMissingValueFiles prevents helm + template from failing when valueFiles do not exist + locally by not appending them to helm template + --values + type: boolean + parameters: + description: Parameters is a list of Helm parameters + which are passed to the helm template command + upon manifest generation + items: + description: HelmParameter is a parameter that's + passed to helm template during manifest generation + properties: + forceString: + description: ForceString determines whether + to tell Helm to interpret booleans and numbers + as strings + type: boolean + name: + description: Name is the name of the Helm + parameter + type: string + value: + description: Value is the value for the Helm + parameter + type: string + type: object + type: array + passCredentials: + description: PassCredentials pass credentials to + all domains (Helm's --pass-credentials) + type: boolean + releaseName: + description: ReleaseName is the Helm release name + to use. If omitted it will use the application + name + type: string + skipCrds: + description: SkipCrds skips custom resource definition + installation step (Helm's --skip-crds) + type: boolean + valueFiles: + description: ValuesFiles is a list of Helm value + files to use when generating a template + items: + type: string + type: array + values: + description: Values specifies Helm values to be + passed to helm template, typically defined as + a block + type: string + version: + description: Version is the Helm version to use + for templating ("3") + type: string + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations is a list of additional + annotations to add to rendered manifests + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels is a list of additional + labels to add to rendered manifests + type: object + forceCommonAnnotations: + description: ForceCommonAnnotations specifies whether + to force applying common annotations to resources + for Kustomize apps + type: boolean + forceCommonLabels: + description: ForceCommonLabels specifies whether + to force applying common labels to resources for + Kustomize apps + type: boolean + images: + description: Images is a list of Kustomize image + override specifications + items: + description: KustomizeImage represents a Kustomize + image definition in the format [old_image_name=]: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to + resources for Kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to + resources for Kustomize apps + type: string + version: + description: Version controls which version of Kustomize + to use for rendering manifests + type: string + type: object + path: + description: Path is a directory path within the Git + repository, and is only valid for applications sourced + from Git. + type: string + plugin: + description: Plugin holds config management plugin specific + options + properties: + env: + description: Env is a list of environment variable + entries + items: + description: EnvEntry represents an entry in the + application's environment + properties: + name: + description: Name is the name of the variable, + usually expressed in uppercase + type: string + value: + description: Value is the value of the variable + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + description: Array is the value of an array + type parameter. + items: + type: string + type: array + map: + additionalProperties: + type: string + description: Map is the value of a map type + parameter. + type: object + name: + description: Name is the name identifying + a parameter. + type: string + string: + description: String_ is the value of a string + type parameter. + type: string + type: object + type: array + type: object + ref: + description: Ref is reference to another source within + sources field. This field will not be used if used + with a `source` tag. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git + or Helm) that contains the application manifests + type: string + targetRevision: + description: TargetRevision defines the revision of + the source to sync the application to. In case of + Git, this can be commit, tag, or branch. If omitted, + will equal to HEAD. In case of Helm, this is a semver + tag for the Chart's version. + type: string + required: + - repoURL + type: object + type: array + required: + - destination + type: object + revision: + description: Revision contains information about the revision + the comparison has been performed to + type: string + revisions: + description: Revisions contains information about the revisions + of multiple sources the comparison has been performed to + items: + type: string + type: array + status: + description: Status is the sync state of the comparison + type: string + required: + - status + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml new file mode 100644 index 0000000000..272bd9e056 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/05_customresourcedefinition_applicationsets.argoproj.io.yaml @@ -0,0 +1,10772 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: applicationsets.argoproj.io + app.kubernetes.io/part-of: argocd + name: applicationsets.argoproj.io +spec: + group: argoproj.io + names: + kind: ApplicationSet + listKind: ApplicationSetList + plural: applicationsets + shortNames: + - appset + - appsets + singular: applicationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + type: object + merge: + properties: + generators: + items: + properties: + clusterDecisionResource: + properties: + configMapRef: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + name: + type: string + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + required: + - configMapRef + type: object + clusters: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + values: + additionalProperties: + type: string + type: object + type: object + git: + properties: + directories: + items: + properties: + exclude: + type: boolean + path: + type: string + required: + - path + type: object + type: array + files: + items: + properties: + path: + type: string + required: + - path + type: object + type: array + pathParamPrefix: + type: string + repoURL: + type: string + requeueAfterSeconds: + format: int64 + type: integer + revision: + type: string + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - repoURL + - revision + type: object + list: + properties: + elements: + items: + x-kubernetes-preserve-unknown-fields: true + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - elements + type: object + matrix: + x-kubernetes-preserve-unknown-fields: true + merge: + x-kubernetes-preserve-unknown-fields: true + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + mergeKeys: + items: + type: string + type: array + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - mergeKeys + type: object + pullRequest: + properties: + bitbucketServer: + properties: + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + repo: + type: string + required: + - api + - project + - repo + type: object + filters: + items: + properties: + branchMatch: + type: string + type: object + type: array + gitea: + properties: + api: + type: string + insecure: + type: boolean + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + - repo + type: object + github: + properties: + api: + type: string + appSecretName: + type: string + labels: + items: + type: string + type: array + owner: + type: string + repo: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - owner + - repo + type: object + gitlab: + properties: + api: + type: string + labels: + items: + type: string + type: array + project: + type: string + pullRequestState: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - project + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + scmProvider: + properties: + azureDevOps: + properties: + accessTokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + allBranches: + type: boolean + api: + type: string + organization: + type: string + teamProject: + type: string + required: + - accessTokenRef + - organization + - teamProject + type: object + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object + bitbucketServer: + properties: + allBranches: + type: boolean + api: + type: string + basicAuth: + properties: + passwordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + username: + type: string + required: + - passwordRef + - username + type: object + project: + type: string + required: + - api + - project + type: object + cloneProtocol: + type: string + filters: + items: + properties: + branchMatch: + type: string + labelMatch: + type: string + pathsDoNotExist: + items: + type: string + type: array + pathsExist: + items: + type: string + type: array + repositoryMatch: + type: string + type: object + type: array + gitea: + properties: + allBranches: + type: boolean + api: + type: string + insecure: + type: boolean + owner: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - api + - owner + type: object + github: + properties: + allBranches: + type: boolean + api: + type: string + appSecretName: + type: string + organization: + type: string + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + gitlab: + properties: + allBranches: + type: boolean + api: + type: string + group: + type: string + includeSubgroups: + type: boolean + tokenRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - group + type: object + requeueAfterSeconds: + format: int64 + type: integer + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + goTemplate: + type: boolean + strategy: + properties: + rollingSync: + properties: + steps: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + maxUpdate: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + type: object + type: + type: string + type: object + syncPolicy: + properties: + preserveResourcesOnDeletion: + type: boolean + type: object + template: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + properties: + destination: + properties: + name: + type: string + namespace: + type: string + server: + type: string + type: object + ignoreDifferences: + items: + properties: + group: + type: string + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + kind: + type: string + managedFieldsManagers: + items: + type: string + type: array + name: + type: string + namespace: + type: string + required: + - kind + type: object + type: array + info: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + type: string + revisionHistoryLimit: + format: int64 + type: integer + source: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + sources: + items: + properties: + chart: + type: string + directory: + properties: + exclude: + type: string + include: + type: string + jsonnet: + properties: + extVars: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + items: + type: string + type: array + tlas: + items: + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + properties: + fileParameters: + items: + properties: + name: + type: string + path: + type: string + type: object + type: array + ignoreMissingValueFiles: + type: boolean + parameters: + items: + properties: + forceString: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + passCredentials: + type: boolean + releaseName: + type: string + skipCrds: + type: boolean + valueFiles: + items: + type: string + type: array + values: + type: string + version: + type: string + type: object + kustomize: + properties: + commonAnnotations: + additionalProperties: + type: string + type: object + commonLabels: + additionalProperties: + type: string + type: object + forceCommonAnnotations: + type: boolean + forceCommonLabels: + type: boolean + images: + items: + type: string + type: array + namePrefix: + type: string + nameSuffix: + type: string + version: + type: string + type: object + path: + type: string + plugin: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + parameters: + items: + properties: + array: + items: + type: string + type: array + map: + additionalProperties: + type: string + type: object + name: + type: string + string: + type: string + type: object + type: array + type: object + ref: + type: string + repoURL: + type: string + targetRevision: + type: string + required: + - repoURL + type: object + type: array + syncPolicy: + properties: + automated: + properties: + allowEmpty: + type: boolean + prune: + type: boolean + selfHeal: + type: boolean + type: object + managedNamespaceMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + retry: + properties: + backoff: + properties: + duration: + type: string + factor: + format: int64 + type: integer + maxDuration: + type: string + type: object + limit: + format: int64 + type: integer + type: object + syncOptions: + items: + type: string + type: array + type: object + required: + - destination + - project + type: object + required: + - metadata + - spec + type: object + required: + - generators + - template + type: object + status: + properties: + applicationStatus: + items: + properties: + application: + type: string + lastTransitionTime: + format: date-time + type: string + message: + type: string + status: + type: string + step: + type: string + required: + - application + - message + - status + - step + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml new file mode 100644 index 0000000000..1ed93a159d --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/06_customresourcedefinition_appprojects.argoproj.io.yaml @@ -0,0 +1,328 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + app.kubernetes.io/name: appprojects.argoproj.io + app.kubernetes.io/part-of: argocd + name: appprojects.argoproj.io +spec: + group: argoproj.io + names: + kind: AppProject + listKind: AppProjectList + plural: appprojects + shortNames: + - appproj + - appprojs + singular: appproject + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'AppProject provides a logical grouping of applications, providing + controls for: * where the apps may deploy to (cluster whitelist) * what + may be deployed (repository whitelist, resource whitelist/blacklist) * who + can access these applications (roles, OIDC group claims bindings) * and + what they can do (RBAC policies) * automation access to these roles (JWT + tokens)' + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppProjectSpec is the specification of an AppProject + properties: + clusterResourceBlacklist: + description: ClusterResourceBlacklist contains list of blacklisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + clusterResourceWhitelist: + description: ClusterResourceWhitelist contains list of whitelisted + cluster level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + description: + description: Description contains optional project description + type: string + destinations: + description: Destinations contains list of destinations available + for deployment + items: + description: ApplicationDestination holds information about the + application's destination + properties: + name: + description: Name is an alternate way of specifying the target + cluster by its symbolic name + type: string + namespace: + description: Namespace specifies the target namespace for the + application's resources. The namespace will only be set for + namespace-scoped resources that have not set a value for .metadata.namespace + type: string + server: + description: Server specifies the URL of the target cluster + and must be set to the Kubernetes control plane API + type: string + type: object + type: array + namespaceResourceBlacklist: + description: NamespaceResourceBlacklist contains list of blacklisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + namespaceResourceWhitelist: + description: NamespaceResourceWhitelist contains list of whitelisted + namespace level resources + items: + description: GroupKind specifies a Group and a Kind, but does not + force a version. This is useful for identifying concepts during + lookup stages without having partially valid types + properties: + group: + type: string + kind: + type: string + required: + - group + - kind + type: object + type: array + orphanedResources: + description: OrphanedResources specifies if controller should monitor + orphaned resources of apps in this project + properties: + ignore: + description: Ignore contains a list of resources that are to be + excluded from orphaned resources monitoring + items: + description: OrphanedResourceKey is a reference to a resource + to be ignored from + properties: + group: + type: string + kind: + type: string + name: + type: string + type: object + type: array + warn: + description: Warn indicates if warning condition should be created + for apps which have orphaned resources + type: boolean + type: object + permitOnlyProjectScopedClusters: + description: PermitOnlyProjectScopedClusters determines whether destinations + can only reference clusters which are project-scoped + type: boolean + roles: + description: Roles are user defined RBAC roles associated with this + project + items: + description: ProjectRole represents a role that has access to a + project + properties: + description: + description: Description is a description of the role + type: string + groups: + description: Groups are a list of OIDC group claims bound to + this role + items: + type: string + type: array + jwtTokens: + description: JWTTokens are a list of generated JWT tokens bound + to this role + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + name: + description: Name is a name for this role + type: string + policies: + description: Policies Stores a list of casbin formatted strings + that define access policies for the role in the project + items: + type: string + type: array + required: + - name + type: object + type: array + signatureKeys: + description: SignatureKeys contains a list of PGP key IDs that commits + in Git must be signed with in order to be allowed for sync + items: + description: SignatureKey is the specification of a key required + to verify commit signatures with + properties: + keyID: + description: The ID of the key in hexadecimal notation + type: string + required: + - keyID + type: object + type: array + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sourceRepos: + description: SourceRepos contains list of repository URLs which can + be used for deployment + items: + type: string + type: array + syncWindows: + description: SyncWindows controls when syncs can be run for apps in + this project + items: + description: SyncWindow contains the kind, time, duration and attributes + that are used to assign the syncWindows to apps + properties: + applications: + description: Applications contains a list of applications that + the window will apply to + items: + type: string + type: array + clusters: + description: Clusters contains a list of clusters that the window + will apply to + items: + type: string + type: array + duration: + description: Duration is the amount of time the sync window + will be open + type: string + kind: + description: Kind defines if the window allows or blocks syncs + type: string + manualSync: + description: ManualSync enables manual syncs when they would + otherwise be blocked + type: boolean + namespaces: + description: Namespaces contains a list of namespaces that the + window will apply to + items: + type: string + type: array + schedule: + description: Schedule is the time the window will begin, specified + in cron format + type: string + timeZone: + description: TimeZone of the sync that will be applied to the + schedule + type: string + type: object + type: array + type: object + status: + description: AppProjectStatus contains status information for AppProject + CRs + properties: + jwtTokensByRole: + additionalProperties: + description: JWTTokens represents a list of JWT tokens + properties: + items: + items: + description: JWTToken holds the issuedAt and expiresAt values + of a token + properties: + exp: + format: int64 + type: integer + iat: + format: int64 + type: integer + id: + type: string + required: + - iat + type: object + type: array + type: object + description: JWTTokensByRole contains a list of JWT tokens issued + for a given role + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml new file mode 100644 index 0000000000..c3248d4740 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/07_customresourcedefinition_argocdexports.argoproj.io.yaml @@ -0,0 +1,257 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocdexports.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCDExport + listKind: ArgoCDExportList + plural: argocdexports + singular: argocdexport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDExport is the Schema for the argocdexports API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDExportSpec defines the desired state of ArgoCDExport + properties: + argocd: + description: Argocd is the name of the ArgoCD instance to export. + type: string + image: + description: Image is the container image to use for the export Job. + type: string + schedule: + description: Schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + type: string + storage: + description: Storage defines the storage configuration options. + properties: + backend: + description: Backend defines the storage backend to use, must + be "local" (the default), "aws", "azure" or "gcp". + type: string + pvc: + description: PVC is the desired characteristics for a PersistentVolumeClaim. + properties: + accessModes: + description: 'AccessModes contains the desired access modes + the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify either: * + An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified data + source, it will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have the + same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which to populate + the volume with data, if a non-empty volume is desired. + This may be any local object from a non-empty API group + (non core object) or a PersistentVolumeClaim object. When + this field is specified, volume binding will only succeed + if the type of the specified object matches some installed + volume populator or dynamic provisioner. This field will + replace the functionality of the DataSource field and as + such if both fields are non-empty, they must have the same + value. For backwards compatibility, both fields (DataSource + and DataSourceRef) will be set to the same value automatically + if one of them is empty and the other is non-empty. There + are two important differences between DataSource and DataSourceRef: + * While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed values (dropping + them), DataSourceRef preserves all values, and generates + an error if a disallowed value is specified. (Alpha) Using + this field requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. If APIGroup is not specified, the specified + Kind must be in the core API group. For any other third-party + types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum resources the + volume should have. If RecoverVolumeExpansionFailure feature + is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher + than capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + secretName: + description: SecretName is the name of a Secret with encryption + key, credentials, etc. + type: string + type: object + version: + description: Version is the tag/digest to use for the export Job container + image. + type: string + required: + - argocd + type: object + status: + description: ArgoCDExportStatus defines the observed state of ArgoCDExport + properties: + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCDExport + is in its lifecycle. There are five possible phase values: Pending: + The ArgoCDExport has been accepted by the Kubernetes system, but + one or more of the required resources have not been created. Running: + All of the containers for the ArgoCDExport are still running, or + in the process of starting or restarting. Succeeded: All containers + for the ArgoCDExport have terminated in success, and will not be + restarted. Failed: At least one container has terminated in failure, + either exited with non-zero status or was terminated by the system. + Unknown: For some reason the state of the ArgoCDExport could not + be obtained.' + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml new file mode 100644 index 0000000000..426a186d4e --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/08_customresourcedefinition_argocds.argoproj.io.yaml @@ -0,0 +1,6443 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + name: argocds.argoproj.io +spec: + group: argoproj.io + names: + kind: ArgoCD + listKind: ArgoCDList + plural: argocds + singular: argocd + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCD is the Schema for the argocds API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ArgoCDSpec defines the desired state of ArgoCD + properties: + applicationInstanceLabelKey: + description: ApplicationInstanceLabelKey is the key name where Argo + CD injects the app name as a tracking label. + type: string + applicationSet: + description: ArgoCDApplicationSet defines whether the Argo CD ApplicationSet + controller should be installed. + properties: + env: + description: Env lets you specify environment for applicationSet + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: ExtraCommandArgs allows users to pass command line + arguments to ApplicationSet controller. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the Argo CD ApplicationSet image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the ApplicationSet controller. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for ApplicationSet. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD ApplicationSet image tag. + (optional) + type: string + webhookServer: + description: WebhookServerSpec defines the options for the ApplicationSet + Webhook Server component. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Application set webhook component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + use for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the + Route resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the + contents of the ca certificate of the final destination. When + using reencrypt termination this file should be + provided in order to have routers use it for health + checks on the secure connection. If this field is + not specified, the router may provide its own destination + CA and perform hostname validation using the short + service name (service.namespace.svc), which allows + infrastructure generated certificates to automatically + verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to + a route. While each router may make its own decisions + on which ports to expose, this is normally port + 80. \n * Allow - traffic is sent to the server on + the insecure port (default) * Disable - no traffic + is allowed on the insecure port. * Redirect - clients + are redirected to the secure port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + type: object + type: object + banner: + description: Banner defines an additional banner to be displayed in + Argo CD UI + properties: + content: + description: Content defines the banner message content to display + type: string + url: + description: URL defines an optional URL to be used as banner + message link + type: string + required: + - content + type: object + configManagementPlugins: + description: ConfigManagementPlugins is used to specify additional + config management plugins. + type: string + controller: + description: Controller defines the Application Controller options + for ArgoCD. + properties: + appSync: + description: "AppSync is used to control the sync frequency, by + default the ArgoCD controller polls Git every 3m. \n Set this + to a duration, e.g. 10m or 600s to control the synchronisation + frequency." + type: string + env: + description: Env lets you specify environment for application + controller pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + logFormat: + description: LogFormat refers to the log format used by the Application + Controller component. Defaults to ArgoCDDefaultLogFormat if + not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level used by the Application + Controller component. Defaults to ArgoCDDefaultLogLevel if not + configured. Valid options are debug, info, error, and warn. + type: string + parallelismLimit: + description: ParallelismLimit defines the limit for parallel kubectl + operations + format: int32 + type: integer + processors: + description: Processors contains the options for the Application + Controller processors. + properties: + operation: + description: Operation is the number of application operation + processors. + format: int32 + type: integer + status: + description: Status is the number of application status processors. + format: int32 + type: integer + type: object + resources: + description: Resources defines the Compute Resources required + by the container for the Application Controller. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + sharding: + description: Sharding contains the options for the Application + Controller sharding configuration. + properties: + enabled: + description: Enabled defines whether sharding should be enabled + on the Application Controller component. + type: boolean + replicas: + description: Replicas defines the number of replicas to run + in the Application controller shard. + format: int32 + type: integer + type: object + type: object + dex: + description: Dex defines the Dex server options for ArgoCD. + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must be a + member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + disableAdmin: + description: DisableAdmin will disable the admin user. + type: boolean + extraConfig: + additionalProperties: + type: string + description: "ExtraConfig can be used to add fields to Argo CD configmap + that are not supported by Argo CD CRD. \n Note: ExtraConfig takes + precedence over Argo CD CRD. For example, A user sets `argocd.Spec.DisableAdmin` + = true and also `a.Spec.ExtraConfig[\"admin.enabled\"]` = true. + In this case, operator updates Argo CD Configmap as follows -> argocd-cm.Data[\"admin.enabled\"] + = true." + type: object + gaAnonymizeUsers: + description: GAAnonymizeUsers toggles user IDs being hashed before + sending to google analytics. + type: boolean + gaTrackingID: + description: GATrackingID is the google analytics tracking ID to use. + type: string + grafana: + description: Grafana defines the Grafana server options for ArgoCD. + properties: + enabled: + description: Enabled will toggle Grafana support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + image: + description: Image is the Grafana container image. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + resources: + description: Resources defines the Compute Resources required + by the container for Grafana. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Grafana component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Grafana Deployment. + format: int32 + type: integer + version: + description: Version is the Grafana container image tag. + type: string + required: + - enabled + type: object + ha: + description: HA options for High Availability support for the Redis + component. + properties: + enabled: + description: Enabled will toggle HA support globally for Argo + CD. + type: boolean + redisProxyImage: + description: RedisProxyImage is the Redis HAProxy container image. + type: string + redisProxyVersion: + description: RedisProxyVersion is the Redis HAProxy container + image tag. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for HA. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - enabled + type: object + helpChatText: + description: HelpChatText is the text for getting chat help, defaults + to "Chat now!" + type: string + helpChatURL: + description: HelpChatURL is the URL for getting chat help, this will + typically be your Slack channel for support. + type: string + image: + description: Image is the ArgoCD container image for all ArgoCD components. + type: string + import: + description: Import is the import/restore options for ArgoCD. + properties: + name: + description: Name of an ArgoCDExport from which to import data. + type: string + namespace: + description: Namespace for the ArgoCDExport, defaults to the same + namespace as the ArgoCD. + type: string + required: + - name + type: object + initialRepositories: + description: InitialRepositories to configure Argo CD with upon creation + of the cluster. + type: string + initialSSHKnownHosts: + description: InitialSSHKnownHosts defines the SSH known hosts data + upon creation of the cluster for connecting Git repositories via + SSH. + properties: + excludedefaulthosts: + description: ExcludeDefaultHosts describes whether you would like + to include the default list of SSH Known Hosts provided by ArgoCD. + type: boolean + keys: + description: Keys describes a custom set of SSH Known Hosts that + you would like to have included in your ArgoCD server. + type: string + type: object + kustomizeBuildOptions: + description: KustomizeBuildOptions is used to specify build options/parameters + to use with `kustomize build`. + type: string + kustomizeVersions: + description: KustomizeVersions is a listing of configured versions + of Kustomize to be made available within ArgoCD. + items: + description: KustomizeVersionSpec is used to specify information + about a kustomize version to be used within ArgoCD. + properties: + path: + description: Path is the path to a configured kustomize version + on the filesystem of your repo server. + type: string + version: + description: Version is a configured kustomize version in the + format of vX.Y.Z + type: string + type: object + type: array + monitoring: + description: Monitoring defines whether workload status monitoring + configuration for this instance. + properties: + enabled: + description: Enabled defines whether workload status monitoring + is enabled for this instance or not + type: boolean + required: + - enabled + type: object + nodePlacement: + description: NodePlacement defines NodeSelectors and Taints for Argo + CD workloads + properties: + nodeSelector: + additionalProperties: + type: string + description: NodeSelector is a field of PodSpec, it is a map of + key value pairs used for node selection + type: object + tolerations: + description: Tolerations allow the pods to schedule onto nodes + with matching taints + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + notifications: + description: Notifications defines whether the Argo CD Notifications + controller should be installed. + properties: + enabled: + description: Enabled defines whether argocd-notifications controller + should be deployed or not + type: boolean + env: + description: Env let you specify environment variables for Notifications + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is the Argo CD Notifications image (optional) + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the argocd-notifications. Defaults to ArgoCDDefaultLogLevel + if not set. Valid options are debug,info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas to run for + notifications-controller + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Argo CD Notifications. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Argo CD Notifications image tag. (optional) + type: string + required: + - enabled + type: object + oidcConfig: + description: OIDCConfig is the OIDC configuration as an alternative + to dex. + type: string + prometheus: + description: Prometheus defines the Prometheus server options for + ArgoCD. + properties: + enabled: + description: Enabled will toggle Prometheus support globally for + ArgoCD. + type: boolean + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Prometheus component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + size: + description: Size is the replica count for the Prometheus StatefulSet. + format: int32 + type: integer + required: + - enabled + type: object + rbac: + description: RBAC defines the RBAC configuration for Argo CD. + properties: + defaultPolicy: + description: DefaultPolicy is the name of the default role which + Argo CD will falls back to, when authorizing API requests (optional). + If omitted or empty, users may be still be able to login, but + will see no apps, projects, etc... + type: string + policy: + description: 'Policy is CSV containing user-defined RBAC policies + and role definitions. Policy rules are in the form: p, subject, + resource, action, object, effect Role definitions and bindings + are in the form: g, subject, inherited-subject See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/rbac.md + for additional information.' + type: string + policyMatcherMode: + description: PolicyMatcherMode configures the matchers function + mode for casbin. There are two options for this, 'glob' for + glob matcher or 'regex' for regex matcher. + type: string + scopes: + description: 'Scopes controls which OIDC scopes to examine during + rbac enforcement (in addition to `sub` scope). If omitted, defaults + to: ''[groups]''.' + type: string + type: object + redis: + description: Redis defines the Redis server options for ArgoCD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the redis server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + disableTLSVerification: + description: DisableTLSVerification defines whether redis server + API should be accessed using strict TLS validation + type: boolean + image: + description: Image is the Redis container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Redis container image tag. + type: string + type: object + repo: + description: Repo defines the repo server options for Argo CD. + properties: + autotls: + description: 'AutoTLS specifies the method to use for automatic + TLS configuration for the repo server The value specified here + can currently be: - openshift - Use the OpenShift service CA + to request TLS config' + type: string + env: + description: Env lets you specify environment for repo server + pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + execTimeout: + description: ExecTimeout specifies the timeout in seconds for + tool execution + type: integer + extraRepoCommandArgs: + description: Extra Command arguments allows users to pass command + line arguments to repo server workload. They get added to default + command line arguments provided by the operator. Please note + that the command line arguments provided as part of ExtraRepoCommandArgs + will not overwrite the default command line arguments. + items: + type: string + type: array + image: + description: Image is the ArgoCD Repo Server container image. + type: string + initContainers: + description: InitContainers defines the list of initialization + containers for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + logFormat: + description: LogFormat describes the log format that should be + used by the Repo Server. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel describes the log level that should be used + by the Repo Server. Defaults to ArgoCDDefaultLogLevel if not + set. Valid options are debug, info, error, and warn. + type: string + mountsatoken: + description: MountSAToken describes whether you would like to + have the Repo server mount the service account token + type: boolean + replicas: + description: Replicas defines the number of replicas for argocd-repo-server. + Value should be greater than or equal to 0. Default is nil. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for Redis. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + serviceaccount: + description: ServiceAccount defines the ServiceAccount user that + you would like the Repo server to use + type: string + sidecarContainers: + description: SidecarContainers defines the list of sidecar containers + for the repo server deployment + items: + description: A single application container that you want to + run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string literal + "$(VAR_NAME)". Escaped references will never be expanded, + regardless of whether the variable exists or not. Cannot + be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. Double $$ are reduced to a single $, which + allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of whether + the variable exists or not. Cannot be updated. More info: + https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management + to default or override container images in workload controllers + like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent + otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The Pod''s termination + grace period countdown begins before the PreStop hook + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period (unless delayed by + finalizers). Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: Deprecated. TCPSocket is NOT supported + as a LifecycleHandler and kept for the backward + compatibility. There are no validation of this + field and lifecycle hooks will fail in runtime + when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. Container + will be restarted if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. Cannot + be updated. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: 'SecurityContext defines the security options + the container should be run with. If set, the fields of + SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that + this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is + windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored + by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the + Pod. All of a Pod's containers must have the same + effective HostProcess value (it is not allowed + to have a mix of HostProcess containers and non-HostProcess + containers). In addition, if HostProcess is true + then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully + initialized. If specified, no other probes are executed + until this completes successfully. If this probe fails, + the Pod will be restarted, just as if the livenessProbe + failed. This can be used to provide different probe parameters + at the beginning of a Pod''s lifecycle, when it might + take a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. This is an alpha field and requires enabling + GRPCContainerProbe feature gate. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + description: "Service is the name of the service + to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + \n If this is not specified, the default behavior + is defined by gRPC." + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully upon probe failure. The grace + period is the duration in seconds after the processes + running in the pod are sent a termination signal and + the time when the processes are forcibly halted with + a kill signal. Set this value longer than the expected + cleanup time for your process. If this value is nil, + the pod's terminationGracePeriodSeconds will be used. + Otherwise, this value overrides the value provided + by the pod spec. Value must be non-negative integer. + The value zero indicates stop immediately via the + kill signal (no opportunity to shut down). This is + a beta field and requires enabling ProbeTerminationGracePeriod + feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds + is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate a buffer + for stdin in the container runtime. If this is not set, + reads from stdin in the container will always result in + EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close + the stdin channel after it has been opened by a single + attach. When stdin is true the stdin stream will remain + open across multiple attach sessions. If stdinOnce is + set to true, stdin is opened on container start, is empty + until the first client attaches to stdin, and then remains + open and accepts data until the client disconnects, at + which time stdin is closed and remains closed until the + container is restarted. If this flag is false, a container + processes that reads from stdin will never receive an + EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which + the container''s termination message will be written is + mounted into the container''s filesystem. Message written + is intended to be brief final status, such as an assertion + failure message. Will be truncated by the node if greater + than 4096 bytes. The total message length across all containers + will be limited to 12kb. Defaults to /dev/termination-log. + Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should + be populated. File will use the contents of terminationMessagePath + to populate the container status message on both success + and failure. FallbackToLogsOnError will use the last chunk + of container log output if the termination message file + is empty and the container exited with an error. The log + output is limited to 2048 bytes or 80 lines, whichever + is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY + for itself, also requires 'stdin' to be true. Default + is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a raw + block device within a container. + properties: + devicePath: + description: devicePath is the path inside of the + container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. Cannot be updated. + type: string + required: + - name + type: object + type: array + verifytls: + description: VerifyTLS defines whether repo server API should + be accessed using strict TLS validation + type: boolean + version: + description: Version is the ArgoCD Repo Server container image + tag. + type: string + volumeMounts: + description: VolumeMounts adds volumeMounts to the repo server + container + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: Volumes adds volumes to the repo server deployment + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob + disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure + Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of + Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the ConfigMap, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed + to the associated CSI driver which will determine + the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all + containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "Ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is + tied to the pod that defines it - it will be created before + the pod starts, and deleted when the pod is removed. \n + Use this if: a) the volume is only needed while the pod + runs, b) features of normal volumes like restoring from + snapshot or capacity tracking are needed, c) the storage + driver is specified through a storage class, and d) the + storage driver supports dynamic volume provisioning through + \ a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'AccessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify + either: * An existing VolumeSnapshot object + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) If + the provisioner or an external controller + can support the specified data source, it + will create a new volume based on the contents + of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always + have the same contents as the DataSourceRef + field.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'Specifies the object from which + to populate the volume with data, if a non-empty + volume is desired. This may be any local object + from a non-empty API group (non core object) + or a PersistentVolumeClaim object. When this + field is specified, volume binding will only + succeed if the type of the specified object + matches some installed volume populator or + dynamic provisioner. This field will replace + the functionality of the DataSource field + and as such if both fields are non-empty, + they must have the same value. For backwards + compatibility, both fields (DataSource and + DataSourceRef) will be set to the same value + automatically if one of them is empty and + the other is non-empty. There are two important + differences between DataSource and DataSourceRef: + * While DataSource only allows two specific + types of objects, DataSourceRef allows any + non-core object, as well as PersistentVolumeClaim + objects. * While DataSource ignores disallowed + values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed + value is specified. (Alpha) Using this field + requires the AnyVolumeDataSource feature gate + to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum + resources the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify + resource requirements that are lower than + previous value but must still be higher than + capacity recorded in the status field of the + claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: A label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: VolumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: FC represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use + for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to + pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the + plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. + Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the volume + directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'Path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will + be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI + transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is + either an IP or ip_addr:port if the port is other + than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits used to set permissions on created + files by default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. Directories within the + path are not affected by this setting. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in + the ScaleIO system that is associated with this volume + source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start + with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys + must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + repositoryCredentials: + description: RepositoryCredentials are the Git pull credentials to + configure Argo CD with upon creation of the cluster. + type: string + resourceActions: + description: ResourceActions customizes resource action behavior. + items: + description: Resource Customization for custom action + properties: + action: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceCustomizations: + description: 'ResourceCustomizations customizes resource behavior. + Keys are in the form: group/Kind. Please note that this is being + deprecated in favor of ResourceHealthChecks, ResourceIgnoreDifferences, + and ResourceActions.' + type: string + resourceExclusions: + description: ResourceExclusions is used to completely ignore entire + classes of resource group/kinds. + type: string + resourceHealthChecks: + description: ResourceHealthChecks customizes resource health check + behavior. + items: + description: Resource Customization for custom health check + properties: + check: + type: string + group: + type: string + kind: + type: string + type: object + type: array + resourceIgnoreDifferences: + description: ResourceIgnoreDifferences customizes resource ignore + difference behavior. + properties: + all: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + resourceIdentifiers: + items: + description: Resource Customization fields for ignore difference + properties: + customization: + properties: + jqPathExpressions: + items: + type: string + type: array + jsonPointers: + items: + type: string + type: array + managedFieldsManagers: + items: + type: string + type: array + type: object + group: + type: string + kind: + type: string + type: object + type: array + type: object + resourceInclusions: + description: ResourceInclusions is used to only include specific group/kinds + in the reconciliation process. + type: string + resourceTrackingMethod: + description: ResourceTrackingMethod defines how Argo CD should track + resources that it manages + type: string + server: + description: Server defines the options for the ArgoCD Server component. + properties: + autoscale: + description: Autoscale defines the autoscale options for the Argo + CD Server component. + properties: + enabled: + description: Enabled will toggle autoscaling support for the + Argo CD Server component. + type: boolean + hpa: + description: HPA defines the HorizontalPodAutoscaler options + for the Argo CD Server component. + properties: + maxReplicas: + description: upper limit for the number of pods that can + be set by the autoscaler; cannot be smaller than MinReplicas. + format: int32 + type: integer + minReplicas: + description: minReplicas is the lower limit for the number + of replicas to which the autoscaler can scale down. It + defaults to 1 pod. minReplicas is allowed to be 0 if + the alpha feature gate HPAScaleToZero is enabled and + at least one Object or External metric is configured. Scaling + is active as long as at least one metric value is available. + format: int32 + type: integer + scaleTargetRef: + description: reference to scaled resource; horizontal + pod autoscaler will learn the current resource consumption + and will set the desired number of pods by using its + Scale subresource. + properties: + apiVersion: + description: API version of the referent + type: string + kind: + description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"' + type: string + name: + description: 'Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + required: + - kind + - name + type: object + targetCPUUtilizationPercentage: + description: target average CPU utilization (represented + as a percentage of requested CPU) over all the pods; + if not specified the default autoscaling policy will + be used. + format: int32 + type: integer + required: + - maxReplicas + - scaleTargetRef + type: object + required: + - enabled + type: object + env: + description: Env lets you specify environment for API server pods + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraCommandArgs: + description: Extra Command arguments that would append to the + Argo CD server command. ExtraCommandArgs will not be added, + if one of these commands is already part of the server command + with same or different value. + items: + type: string + type: array + grpc: + description: GRPC defines the state for the Argo CD Server GRPC + options. + properties: + host: + description: Host is the hostname to use for Ingress/Route + resources. + type: string + ingress: + description: Ingress defines the desired state for the Argo + CD Server GRPC Ingress. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to + apply to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + type: object + host: + description: Host is the hostname to use for Ingress/Route resources. + type: string + ingress: + description: Ingress defines the desired state for an Ingress + for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to apply + to the Ingress. + type: object + enabled: + description: Enabled will toggle the creation of the Ingress. + type: boolean + ingressClassName: + description: IngressClassName for the Ingress resource. + type: string + path: + description: Path used for the Ingress resource. + type: string + tls: + description: TLS configuration. Currently the Ingress only + supports a single TLS port, 443. If multiple members of + this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified through + the SNI TLS extension, if the ingress controller fulfilling + the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the + TLS certificate. The values in this list must match + the name/s used in the tlsSecret. Defaults to the + wildcard host setting for the loadbalancer controller + fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret used + to terminate TLS traffic on port 443. Field is left + optional to allow TLS routing based on SNI hostname + alone. If the SNI host in a listener conflicts with + the "Host" header field used by an IngressRule, the + SNI host is used for termination and value of the + Host header is used for routing. + type: string + type: object + type: array + required: + - enabled + type: object + insecure: + description: Insecure toggles the insecure flag. + type: boolean + logFormat: + description: LogFormat refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogFormat + if not configured. Valid options are text or json. + type: string + logLevel: + description: LogLevel refers to the log level to be used by the + ArgoCD Server component. Defaults to ArgoCDDefaultLogLevel if + not set. Valid options are debug, info, error, and warn. + type: string + replicas: + description: Replicas defines the number of replicas for argocd-server. + Default is nil. Value should be greater than or equal to 0. + Value will be ignored if Autoscaler is enabled. + format: int32 + type: integer + resources: + description: Resources defines the Compute Resources required + by the container for the Argo CD server component. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + route: + description: Route defines the desired state for an OpenShift + Route for the Argo CD Server component. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is the map of annotations to use + for the Route resource. + type: object + enabled: + description: Enabled will toggle the creation of the OpenShift + Route. + type: boolean + labels: + additionalProperties: + type: string + description: Labels is the map of labels to use for the Route + resource + type: object + path: + description: Path the router watches for, to route traffic + for to the service. + type: string + tls: + description: TLS provides the ability to configure certificates + and termination for the Route. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents + type: string + destinationCACertificate: + description: destinationCACertificate provides the contents + of the ca certificate of the final destination. When + using reencrypt termination this file should be provided + in order to have routers use it for health checks on + the secure connection. If this field is not specified, + the router may provide its own destination CA and perform + hostname validation using the short service name (service.namespace.svc), + which allows infrastructure generated certificates to + automatically verify. + type: string + insecureEdgeTerminationPolicy: + description: "insecureEdgeTerminationPolicy indicates + the desired behavior for insecure connections to a route. + While each router may make its own decisions on which + ports to expose, this is normally port 80. \n * Allow + - traffic is sent to the server on the insecure port + (default) * Disable - no traffic is allowed on the insecure + port. * Redirect - clients are redirected to the secure + port." + type: string + key: + description: key provides key file contents + type: string + termination: + description: termination indicates termination type. + type: string + required: + - termination + type: object + wildcardPolicy: + description: WildcardPolicy if any for the route. Currently + only 'Subdomain' or 'None' is allowed. + type: string + required: + - enabled + type: object + service: + description: Service defines the options for the Service backing + the ArgoCD Server component. + properties: + type: + description: Type is the ServiceType to use for the Service + resource. + type: string + required: + - type + type: object + type: object + sourceNamespaces: + description: SourceNamespaces defines the namespaces application resources + are allowed to be created in + items: + type: string + type: array + sso: + description: SSO defines the Single Sign-on configuration for Argo + CD + properties: + dex: + description: Dex contains the configuration for Argo CD dex authentication + properties: + config: + description: Config is the dex connector configuration. + type: string + groups: + description: Optional list of required groups a user must + be a member of + items: + type: string + type: array + image: + description: Image is the Dex container image. + type: string + openShiftOAuth: + description: OpenShiftOAuth enables OpenShift OAuth authentication + for the Dex server. + type: boolean + resources: + description: Resources defines the Compute Resources required + by the container for Dex. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + version: + description: Version is the Dex container image tag. + type: string + type: object + image: + description: Image is the SSO container image. + type: string + keycloak: + description: Keycloak contains the configuration for Argo CD keycloak + authentication + properties: + image: + description: Image is the Keycloak container image. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for Keycloak. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + rootCA: + description: Custom root CA certificate for communicating + with the Keycloak OIDC provider + type: string + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the Keycloak container image tag. + type: string + type: object + provider: + description: Provider installs and configures the given SSO Provider + with Argo CD. + type: string + resources: + description: Resources defines the Compute Resources required + by the container for SSO. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + verifyTLS: + description: VerifyTLS set to false disables strict TLS validation. + type: boolean + version: + description: Version is the SSO container image tag. + type: string + type: object + statusBadgeEnabled: + description: StatusBadgeEnabled toggles application status badge feature. + type: boolean + tls: + description: TLS defines the TLS options for ArgoCD. + properties: + ca: + description: CA defines the CA options. + properties: + configMapName: + description: ConfigMapName is the name of the ConfigMap containing + the CA Certificate. + type: string + secretName: + description: SecretName is the name of the Secret containing + the CA Certificate and Key. + type: string + type: object + initialCerts: + additionalProperties: + type: string + description: InitialCerts defines custom TLS certificates upon + creation of the cluster for connecting Git repositories via + HTTPS. + type: object + type: object + usersAnonymousEnabled: + description: UsersAnonymousEnabled toggles anonymous user access. + The anonymous users get default role permissions specified argocd-rbac-cm. + type: boolean + version: + description: Version is the tag to use with the ArgoCD container image + for all ArgoCD components. + type: string + type: object + status: + description: ArgoCDStatus defines the observed state of ArgoCD + properties: + applicationController: + description: 'ApplicationController is a simple, high-level summary + of where the Argo CD application controller component is in its + lifecycle. There are four possible ApplicationController values: + Pending: The Argo CD application controller component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD application controller component are in a Ready state. Failed: + At least one of the Argo CD application controller component Pods + had a failure. Unknown: The state of the Argo CD application controller + component could not be obtained.' + type: string + applicationSetController: + description: 'ApplicationSetController is a simple, high-level summary + of where the Argo CD applicationSet controller component is in its + lifecycle. There are four possible ApplicationSetController values: + Pending: The Argo CD applicationSet controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD applicationSet controller component are in a Ready + state. Failed: At least one of the Argo CD applicationSet controller + component Pods had a failure. Unknown: The state of the Argo CD + applicationSet controller component could not be obtained.' + type: string + dex: + description: 'Dex is a simple, high-level summary of where the Argo + CD Dex component is in its lifecycle. There are four possible dex + values: Pending: The Argo CD Dex component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Dex component are in a Ready state. Failed: At least one + of the Argo CD Dex component Pods had a failure. Unknown: The state + of the Argo CD Dex component could not be obtained.' + type: string + host: + description: Host is the hostname of the Ingress. + type: string + notificationsController: + description: 'NotificationsController is a simple, high-level summary + of where the Argo CD notifications controller component is in its + lifecycle. There are four possible NotificationsController values: + Pending: The Argo CD notifications controller component has been + accepted by the Kubernetes system, but one or more of the required + resources have not been created. Running: All of the required Pods + for the Argo CD notifications controller component are in a Ready + state. Failed: At least one of the Argo CD notifications controller + component Pods had a failure. Unknown: The state of the Argo CD + notifications controller component could not be obtained.' + type: string + phase: + description: 'Phase is a simple, high-level summary of where the ArgoCD + is in its lifecycle. There are four possible phase values: Pending: + The ArgoCD has been accepted by the Kubernetes system, but one or + more of the required resources have not been created. Available: + All of the resources for the ArgoCD are ready. Failed: At least + one resource has experienced a failure. Unknown: The state of the + ArgoCD phase could not be obtained.' + type: string + redis: + description: 'Redis is a simple, high-level summary of where the Argo + CD Redis component is in its lifecycle. There are four possible + redis values: Pending: The Argo CD Redis component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Redis component are in a Ready state. Failed: At least one + of the Argo CD Redis component Pods had a failure. Unknown: The + state of the Argo CD Redis component could not be obtained.' + type: string + redisTLSChecksum: + description: RedisTLSChecksum contains the SHA256 checksum of the + latest known state of tls.crt and tls.key in the argocd-operator-redis-tls + secret. + type: string + repo: + description: 'Repo is a simple, high-level summary of where the Argo + CD Repo component is in its lifecycle. There are four possible repo + values: Pending: The Argo CD Repo component has been accepted by + the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD Repo component are in a Ready state. Failed: At least one + of the Argo CD Repo component Pods had a failure. Unknown: The + state of the Argo CD Repo component could not be obtained.' + type: string + repoTLSChecksum: + description: RepoTLSChecksum contains the SHA256 checksum of the latest + known state of tls.crt and tls.key in the argocd-repo-server-tls + secret. + type: string + server: + description: 'Server is a simple, high-level summary of where the + Argo CD server component is in its lifecycle. There are four possible + server values: Pending: The Argo CD server component has been accepted + by the Kubernetes system, but one or more of the required resources + have not been created. Running: All of the required Pods for the + Argo CD server component are in a Ready state. Failed: At least + one of the Argo CD server component Pods had a failure. Unknown: + The state of the Argo CD server component could not be obtained.' + type: string + ssoConfig: + description: 'SSOConfig defines the status of SSO configuration. Success: + Only one SSO provider is configured in CR. Failed: SSO configuration + is illegal or more than one SSO providers are configured in CR. + Unknown: The SSO configuration could not be obtained.' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/09_deployment_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/09_deployment_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..284bc54774 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/09_deployment_argocd-operator-controller-manager.yaml @@ -0,0 +1,204 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system +spec: + replicas: 1 + revisionHistoryLimit: 1 + selector: + matchLabels: + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "AppProject", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Application", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ApplicationSet", + "metadata": { + "name": "example" + }, + "spec": null + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCD", + "metadata": { + "name": "argocd-sample" + }, + "spec": { + "controller": { + "resources": { + "limits": { + "cpu": "2000m", + "memory": "2048Mi" + }, + "requests": { + "cpu": "250m", + "memory": "1024Mi" + } + } + }, + "ha": { + "enabled": false, + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "redis": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "repo": { + "resources": { + "limits": { + "cpu": "1000m", + "memory": "512Mi" + }, + "requests": { + "cpu": "250m", + "memory": "256Mi" + } + } + }, + "server": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "125m", + "memory": "128Mi" + } + }, + "route": { + "enabled": true + } + }, + "sso": { + "dex": { + "resources": { + "limits": { + "cpu": "500m", + "memory": "256Mi" + }, + "requests": { + "cpu": "250m", + "memory": "128Mi" + } + } + }, + "provider": "dex" + } + } + }, + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "ArgoCDExport", + "metadata": { + "name": "argocdexport-sample" + }, + "spec": { + "argocd": "argocd-sample" + } + } + ] + capabilities: Deep Insights + categories: Integration & Delivery + certified: "false" + containerImage: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + description: Argo CD is a declarative, GitOps continuous delivery tool for + Kubernetes. + olm.targetNamespaces: argocd-watch + operators.operatorframework.io/builder: operator-sdk-v1.10.0+git + operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 + repository: https://github.com/argoproj-labs/argocd-operator + support: Argo CD + labels: + control-plane: controller-manager + spec: + containers: + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + resources: {} + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: quay.io/argoprojlabs/argocd-operator@sha256:99aeec24cc406d06d18822347d9ac3ed053a702d8419191e4e681075fed7b9bb + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + securityContext: + runAsNonRoot: true + serviceAccountName: argocd-operator-controller-manager + terminationGracePeriodSeconds: 10 +status: {} diff --git a/config/base/rbac/leader_election_role.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml similarity index 80% rename from config/base/rbac/leader_election_role.yaml rename to test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml index 4190ec8059..cd9667a38a 100644 --- a/config/base/rbac/leader_election_role.yaml +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/10_role_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml @@ -1,8 +1,8 @@ -# permissions to do leader election. apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: leader-election-role + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + namespace: argocd-watch rules: - apiGroups: - "" diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml new file mode 100644 index 0000000000..6ba70f3e58 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/11_rolebinding_argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 + namespace: argocd-watch +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 +subjects: +- kind: ServiceAccount + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml new file mode 100644 index 0000000000..e69c19f45a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/12_service_argocd-operator-controller-manager-metrics-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + control-plane: controller-manager + name: argocd-operator-controller-manager-metrics-service + namespace: argocd-system +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml new file mode 100644 index 0000000000..8e5212c47c --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/argocd-operator.v0.6.0/single-namespace/13_serviceaccount_argocd-operator-controller-manager.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argocd-operator-controller-manager + namespace: argocd-system diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/00_clusterrole_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/00_clusterrole_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml new file mode 100644 index 0000000000..04c71ff75b --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/00_clusterrole_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve +rules: +- apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests/finalizers + verbs: + - update +- apiGroups: + - webhook.operators.coreos.io + resources: + - webhooktests/status + verbs: + - get + - patch + - update +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/01_clusterrole_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/01_clusterrole_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml new file mode 100644 index 0000000000..9ade65d095 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/01_clusterrole_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml @@ -0,0 +1,44 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3 +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/02_clusterrolebinding_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/02_clusterrolebinding_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml new file mode 100644 index 0000000000..ad6f8f42c1 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/02_clusterrolebinding_webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: webhook-operator.v-119edugfdvz48oyy2ctmbp4ec5c4a7zbv5k03vg4y3ve +subjects: +- kind: ServiceAccount + name: webhook-operator-controller-manager + namespace: webhook-system diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/03_clusterrolebinding_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/03_clusterrolebinding_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml new file mode 100644 index 0000000000..151d7b374a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/03_clusterrolebinding_webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: webhook-operator.v-1zfvwth88plw3th2midra1jqduroo1q7omiagjujsiu3 +subjects: +- kind: ServiceAccount + name: webhook-operator-controller-manager + namespace: webhook-system diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/04_customresourcedefinition_webhooktests.webhook.operators.coreos.io.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/04_customresourcedefinition_webhooktests.webhook.operators.coreos.io.yaml new file mode 100644 index 0000000000..f8b20005e9 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/04_customresourcedefinition_webhooktests.webhook.operators.coreos.io.yaml @@ -0,0 +1,272 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: webhook-operator-system/webhook-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.19.0 + name: webhooktests.webhook.operators.coreos.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: webhook-operator-controller-manager-service + namespace: webhook-system + path: /convert + port: 443 + conversionReviewVersions: + - v1 + group: webhook.operators.coreos.io + names: + kind: WebhookTest + listKind: WebhookTestList + plural: webhooktests + singular: webhooktest + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: WebhookTest is the Schema for the webhooktests API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of WebhookTest + properties: + mutate: + description: Mutate is a field that will be set to true by the mutating + webhook. + type: boolean + valid: + description: Valid must be set to true or the validation webhook will + reject the resource. + type: boolean + required: + - valid + type: object + status: + description: status defines the observed state of WebhookTest + properties: + conditions: + description: |- + conditions represent the current state of the WebhookTest resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: WebhookTest is the Schema for the webhooktests API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of WebhookTest + properties: + conversion: + description: Conversion is an example field of WebhookTest. Edit WebhookTest_types.go + to remove/update + properties: + mutate: + description: Mutate is a field that will be set to true by the + mutating webhook. + type: boolean + valid: + description: Valid must be set to true or the validation webhook + will reject the resource. + type: boolean + required: + - valid + type: object + required: + - conversion + type: object + status: + description: status defines the observed state of WebhookTest + properties: + conditions: + description: |- + conditions represent the current state of the WebhookTest resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/05_deployment_webhook-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/05_deployment_webhook-operator-controller-manager.yaml new file mode 100644 index 0000000000..57b5d7e17a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/05_deployment_webhook-operator-controller-manager.yaml @@ -0,0 +1,110 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + name: webhook-operator-controller-manager + namespace: webhook-system +spec: + replicas: 1 + revisionHistoryLimit: 1 + selector: + matchLabels: + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + strategy: {} + template: + metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "WebhookTest", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "webhook-operator" + }, + "name": "webhooktest-sample" + }, + "spec": null + }, + { + "apiVersion": "webhook.operators.coreos.io/v2", + "kind": "WebhookTest", + "metadata": { + "labels": { + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "webhook-operator" + }, + "name": "webhooktest-sample" + }, + "spec": null + } + ] + capabilities: Basic Install + kubectl.kubernetes.io/default-container: manager + olm.targetNamespaces: "" + operators.operatorframework.io/builder: operator-sdk-v1.41.1 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + labels: + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager + spec: + containers: + - args: + - --metrics-bind-address=:8443 + - --leader-elect + - --health-probe-bind-address=:8081 + - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs + command: + - /manager + image: quay.io/olmtest/webhook-operator:v0.0.5 + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: webhook-certs + readOnly: true + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccountName: webhook-operator-controller-manager + terminationGracePeriodSeconds: 10 + volumes: + - name: webhook-certs + secret: + secretName: webhook-server-cert +status: {} diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/06_mutatingwebhookconfiguration_mwebhooktest-v1.kb.io.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/06_mutatingwebhookconfiguration_mwebhooktest-v1.kb.io.yaml new file mode 100644 index 0000000000..fda75e085e --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/06_mutatingwebhookconfiguration_mwebhooktest-v1.kb.io.yaml @@ -0,0 +1,27 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mwebhooktest-v1.kb.io + namespace: webhook-system +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-operator-controller-manager-service + namespace: webhook-system + path: /mutate-webhook-operators-coreos-io-v1-webhooktest + port: 443 + failurePolicy: Fail + name: mwebhooktest-v1.kb.io + rules: + - apiGroups: + - webhook.operators.coreos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - webhooktests + sideEffects: None diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/07_service_webhook-operator-controller-manager-service.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/07_service_webhook-operator-controller-manager-service.yaml new file mode 100644 index 0000000000..6c3a029fa3 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/07_service_webhook-operator-controller-manager-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: webhook-operator-controller-manager-service + namespace: webhook-system +spec: + ports: + - name: "443" + port: 443 + targetPort: 9443 + selector: + app.kubernetes.io/name: webhook-operator + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/08_serviceaccount_webhook-operator-controller-manager.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/08_serviceaccount_webhook-operator-controller-manager.yaml new file mode 100644 index 0000000000..e709377659 --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/08_serviceaccount_webhook-operator-controller-manager.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: webhook-operator-controller-manager + namespace: webhook-system diff --git a/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/09_validatingwebhookconfiguration_vwebhooktest-v1.kb.io.yaml b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/09_validatingwebhookconfiguration_vwebhooktest-v1.kb.io.yaml new file mode 100644 index 0000000000..2939861f1a --- /dev/null +++ b/test/regression/convert/testdata/expected-manifests/webhook-operator.v0.0.5/all-webhook-types/09_validatingwebhookconfiguration_vwebhooktest-v1.kb.io.yaml @@ -0,0 +1,27 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: vwebhooktest-v1.kb.io + namespace: webhook-system +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-operator-controller-manager-service + namespace: webhook-system + path: /validate-webhook-operators-coreos-io-v1-webhooktest + port: 443 + failurePolicy: Fail + name: vwebhooktest-v1.kb.io + rules: + - apiGroups: + - webhook.operators.coreos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - webhooktests + sideEffects: None diff --git a/test/upgrade-e2e/post_upgrade_test.go b/test/upgrade-e2e/post_upgrade_test.go index 751730b8a1..785d91ea3b 100644 --- a/test/upgrade-e2e/post_upgrade_test.go +++ b/test/upgrade-e2e/post_upgrade_test.go @@ -18,77 +18,160 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - catalogdv1alpha1 "github.com/operator-framework/catalogd/api/core/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" +) - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" +const ( + artifactName = "operator-controller-upgrade-e2e" + container = "manager" ) -func TestClusterExtensionAfterOLMUpgrade(t *testing.T) { - t.Log("Starting checks after OLM upgrade") +func TestClusterCatalogUnpacking(t *testing.T) { ctx := context.Background() - managerLabelSelector := labels.Set{"control-plane": "operator-controller-controller-manager"} - t.Log("Checking that the controller-manager deployment is updated") + managerLabelSelector := labels.Set{"app.kubernetes.io/name": "catalogd"} + var managerDeployment appsv1.Deployment require.EventuallyWithT(t, func(ct *assert.CollectT) { var managerDeployments appsv1.DeploymentList - assert.NoError(ct, c.List(ctx, &managerDeployments, client.MatchingLabelsSelector{Selector: managerLabelSelector.AsSelector()})) - assert.Len(ct, managerDeployments.Items, 1) - managerDeployment := managerDeployments.Items[0] + err := c.List(ctx, &managerDeployments, client.MatchingLabels(managerLabelSelector), client.InNamespace("olmv1-system")) + require.NoError(ct, err) + require.Len(ct, managerDeployments.Items, 1) + managerDeployment = managerDeployments.Items[0] + require.Equal(ct, *managerDeployment.Spec.Replicas, managerDeployment.Status.UpdatedReplicas) + require.Equal(ct, *managerDeployment.Spec.Replicas, managerDeployment.Status.Replicas) + require.Equal(ct, *managerDeployment.Spec.Replicas, managerDeployment.Status.AvailableReplicas) + require.Equal(ct, *managerDeployment.Spec.Replicas, managerDeployment.Status.ReadyReplicas) + }, time.Minute, time.Second) - assert.True(ct, - managerDeployment.Status.UpdatedReplicas == *managerDeployment.Spec.Replicas && - managerDeployment.Status.Replicas == *managerDeployment.Spec.Replicas && - managerDeployment.Status.AvailableReplicas == *managerDeployment.Spec.Replicas && - managerDeployment.Status.ReadyReplicas == *managerDeployment.Spec.Replicas, - ) + var managerPod corev1.Pod + t.Log("Waiting for only one controller-manager pod to remain") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + var managerPods corev1.PodList + err := c.List(ctx, &managerPods, client.MatchingLabels(managerLabelSelector)) + require.NoError(ct, err) + require.Len(ct, managerPods.Items, 1) + managerPod = managerPods.Items[0] }, time.Minute, time.Second) - var managerPods corev1.PodList - t.Log("Waiting for only one controller-manager Pod to remain") + t.Log("Waiting for acquired leader election") + leaderCtx, leaderCancel := context.WithTimeout(ctx, 3*time.Minute) + defer leaderCancel() + leaderSubstrings := []string{"successfully acquired lease"} + leaderElected, err := watchPodLogsForSubstring(leaderCtx, &managerPod, leaderSubstrings...) + require.NoError(t, err) + require.True(t, leaderElected) + + t.Log("Reading logs to make sure that ClusterCatalog was reconciled by catalogdv1") + logCtx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + substrings := []string{ + "reconcile ending", + fmt.Sprintf(`ClusterCatalog=%q`, testClusterCatalogName), + } + found, err := watchPodLogsForSubstring(logCtx, &managerPod, substrings...) + require.NoError(t, err) + require.True(t, found) + + catalog := &ocv1.ClusterCatalog{} + t.Log("Ensuring ClusterCatalog has Status.Condition of Progressing with a status == True, reason == Succeeded") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.List(ctx, &managerPods, client.MatchingLabelsSelector{Selector: managerLabelSelector.AsSelector()})) - assert.Len(ct, managerPods.Items, 1) + err := c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, catalog) + require.NoError(ct, err) + cond := apimeta.FindStatusCondition(catalog.Status.Conditions, ocv1.TypeProgressing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) }, time.Minute, time.Second) + t.Log("Ensuring ClusterCatalog has Status.Condition of Serving with a status == True, reason == Available") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + err := c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, catalog) + require.NoError(ct, err) + cond := apimeta.FindStatusCondition(catalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, time.Minute, time.Second) +} + +func TestClusterExtensionAfterOLMUpgrade(t *testing.T) { + t.Log("Starting checks after OLM upgrade") + ctx := context.Background() + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + // wait for catalogd deployment to finish + t.Log("Wait for catalogd deployment to be ready") + catalogdManagerPod := waitForDeployment(t, ctx, "catalogd") + + // wait for operator-controller deployment to finish + t.Log("Wait for operator-controller deployment to be ready") + managerPod := waitForDeployment(t, ctx, "operator-controller") + + t.Log("Wait for acquired leader election") + // Average case is under 1 minute but in the worst case: (previous leader crashed) + // we could have LeaseDuration (137s) + RetryPeriod (26s) +/- 163s + leaderCtx, leaderCancel := context.WithTimeout(ctx, 3*time.Minute) + defer leaderCancel() + + leaderSubstrings := []string{"successfully acquired lease"} + leaderElected, err := watchPodLogsForSubstring(leaderCtx, managerPod, leaderSubstrings...) + require.NoError(t, err) + require.True(t, leaderElected) + t.Log("Reading logs to make sure that ClusterExtension was reconciled by operator-controller before we update it") // Make sure that after we upgrade OLM itself we can still reconcile old objects without any changes logCtx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() substrings := []string{ "reconcile ending", - fmt.Sprintf(`"ClusterExtension": {"name":"%s"}`, testClusterExtensionName), + fmt.Sprintf(`ClusterExtension=%q`, testClusterExtensionName), } - found, err := watchPodLogsForSubstring(logCtx, &managerPods.Items[0], "manager", substrings...) + found, err := watchPodLogsForSubstring(logCtx, managerPod, substrings...) require.NoError(t, err) require.True(t, found) t.Log("Checking that the ClusterCatalog is unpacked") require.EventuallyWithT(t, func(ct *assert.CollectT) { - var clusterCatalog catalogdv1alpha1.ClusterCatalog - assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, &clusterCatalog)) - cond := apimeta.FindStatusCondition(clusterCatalog.Status.Conditions, catalogdv1alpha1.TypeUnpacked) - if !assert.NotNil(ct, cond) { + var clusterCatalog ocv1.ClusterCatalog + require.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, &clusterCatalog)) + + // check serving condition + cond := apimeta.FindStatusCondition(clusterCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + + // mitigation for upgrade-e2e flakiness caused by the following bug + // https://github.com/operator-framework/operator-controller/issues/1626 + // wait until the unpack time > than the catalogd controller pod creation time + cond = apimeta.FindStatusCondition(clusterCatalog.Status.Conditions, ocv1.TypeProgressing) + if cond == nil { return } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, catalogdv1alpha1.ReasonUnpackSuccessful, cond.Reason) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + + require.True(ct, clusterCatalog.Status.LastUnpacked.After(catalogdManagerPod.CreationTimestamp.Time)) }, time.Minute, time.Second) + // TODO: if we change the underlying revision storage mechanism, the new version + // will not detect any installed versions, we need to make sure that the upgrade + // test fails across revision storage mechanism changes that are not also accompanied + // by code that automatically migrates the revision storage. + t.Log("Checking that the ClusterExtension is installed") - var clusterExtension ocv1alpha1.ClusterExtension + var clusterExtension ocv1.ClusterExtension require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, metav1.ConditionTrue, cond.Status) - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "Installed bundle") - if assert.NotEmpty(ct, clusterExtension.Status.Install.Bundle) { - assert.NotEmpty(ct, clusterExtension.Status.Install.Bundle.Version) - } + require.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle.Version) }, time.Minute, time.Second) previousVersion := clusterExtension.Status.Install.Bundle.Version @@ -100,20 +183,50 @@ func TestClusterExtensionAfterOLMUpgrade(t *testing.T) { t.Log("Checking that the ClusterExtension installs successfully") require.EventuallyWithT(t, func(ct *assert.CollectT) { - assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - if !assert.NotNil(ct, cond) { - return - } - assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "Installed bundle") - assert.Equal(ct, ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.1", Version: "1.0.1"}, clusterExtension.Status.Resolution.Bundle) - assert.Equal(ct, ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.1", Version: "1.0.1"}, clusterExtension.Status.Install.Bundle) - assert.NotEqual(ct, previousVersion, clusterExtension.Status.Install.Bundle.Version) + require.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.Equal(ct, ocv1.BundleMetadata{Name: "test-operator.1.0.1", Version: "1.0.1"}, clusterExtension.Status.Install.Bundle) + require.NotEqual(ct, previousVersion, clusterExtension.Status.Install.Bundle.Version) + }, time.Minute, time.Second) +} + +// waitForDeployment checks that the updated deployment with the given app.kubernetes.io/name label +// has reached the desired number of replicas and that the number pods matches that number +// i.e. no old pods remain. It will return a pointer to the first pod. This is only necessary +// to facilitate the mitigation put in place for https://github.com/operator-framework/operator-controller/issues/1626 +func waitForDeployment(t *testing.T, ctx context.Context, controlPlaneLabel string) *corev1.Pod { + deploymentLabelSelector := labels.Set{"app.kubernetes.io/name": controlPlaneLabel}.AsSelector() + + t.Log("Checking that the deployment is updated") + var desiredNumReplicas int32 + require.EventuallyWithT(t, func(ct *assert.CollectT) { + var managerDeployments appsv1.DeploymentList + require.NoError(ct, c.List(ctx, &managerDeployments, client.MatchingLabelsSelector{Selector: deploymentLabelSelector})) + require.Len(ct, managerDeployments.Items, 1) + managerDeployment := managerDeployments.Items[0] + + require.True(ct, + managerDeployment.Status.UpdatedReplicas == *managerDeployment.Spec.Replicas && + managerDeployment.Status.Replicas == *managerDeployment.Spec.Replicas && + managerDeployment.Status.AvailableReplicas == *managerDeployment.Spec.Replicas && + managerDeployment.Status.ReadyReplicas == *managerDeployment.Spec.Replicas, + ) + desiredNumReplicas = *managerDeployment.Spec.Replicas + }, time.Minute, time.Second) + + var managerPods corev1.PodList + t.Logf("Ensure the number of remaining pods equal the desired number of replicas (%d)", desiredNumReplicas) + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.List(ctx, &managerPods, client.MatchingLabelsSelector{Selector: deploymentLabelSelector})) + require.Len(ct, managerPods.Items, 1) }, time.Minute, time.Second) + return &managerPods.Items[0] } -func watchPodLogsForSubstring(ctx context.Context, pod *corev1.Pod, container string, substrings ...string) (bool, error) { +func watchPodLogsForSubstring(ctx context.Context, pod *corev1.Pod, substrings ...string) (bool, error) { podLogOpts := corev1.PodLogOptions{ Follow: true, Container: container, diff --git a/test/upgrade-e2e/upgrade_e2e_suite_test.go b/test/upgrade-e2e/upgrade_e2e_suite_test.go index 3283265af8..a2acee4cda 100644 --- a/test/upgrade-e2e/upgrade_e2e_suite_test.go +++ b/test/upgrade-e2e/upgrade_e2e_suite_test.go @@ -6,10 +6,11 @@ import ( "testing" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/operator-framework/operator-controller/internal/scheme" + "github.com/operator-framework/operator-controller/internal/operator-controller/scheme" ) const ( @@ -21,12 +22,14 @@ var ( c client.Client kclientset kubernetes.Interface + cfg *rest.Config testClusterCatalogName string testClusterExtensionName string ) func TestMain(m *testing.M) { var ok bool + cfg = ctrl.GetConfigOrDie() testClusterCatalogName, ok = os.LookupEnv(testClusterCatalogNameEnv) if !ok { fmt.Printf("%q is not set", testClusterCatalogNameEnv) diff --git a/test/utils.go b/test/utils.go new file mode 100644 index 0000000000..22a50b2b8d --- /dev/null +++ b/test/utils.go @@ -0,0 +1,54 @@ +package test + +import ( + "os" + "path" + "path/filepath" + "runtime" + + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +// NewEnv creates a new envtest.Environment instance. +func NewEnv() *envtest.Environment { + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{ + pathFromProjectRoot("helm/olmv1/base/operator-controller/crd/experimental"), + }, + ErrorIfCRDPathMissing: true, + } + // ENVTEST-based tests require specific binaries. By default, these binaries are located + // in paths defined by controller-runtime. However, the `BinaryAssetsDirectory` needs + // to be explicitly set when running tests directly (e.g., debugging tests in an IDE) + // without using the Makefile targets. + // + // This is equivalent to configuring your IDE to export the `KUBEBUILDER_ASSETS` environment + // variable before each test execution. The following function simplifies this process + // by handling the configuration for you. + // + // To ensure the binaries are in the expected path without manual configuration, run: + // `make envtest-k8s-bins` + if getFirstFoundEnvTestBinaryDir() != "" { + testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() + } + return testEnv +} + +// pathFromProjectRoot returns the absolute path to the given relative path from the project root. +func pathFromProjectRoot(relativePath string) string { + _, filename, _, _ := runtime.Caller(0) + p := path.Join(path.Dir(path.Dir(filename)), relativePath) + return p +} + +// getFirstFoundEnvTestBinaryDir finds and returns the first directory under the given path. +func getFirstFoundEnvTestBinaryDir() string { + basePath := pathFromProjectRoot(filepath.Join("bin", "envtest-binaries", "k8s")) + entries, _ := os.ReadDir(basePath) + for _, entry := range entries { + if entry.IsDir() { + return filepath.Join(basePath, entry.Name()) + } + } + return "" +} diff --git a/testdata/.gitignore b/testdata/.gitignore new file mode 100644 index 0000000000..8e0dcaba17 --- /dev/null +++ b/testdata/.gitignore @@ -0,0 +1 @@ +push/bin diff --git a/testdata/Dockerfile b/testdata/Dockerfile new file mode 100644 index 0000000000..0bee4012bf --- /dev/null +++ b/testdata/Dockerfile @@ -0,0 +1,9 @@ +FROM gcr.io/distroless/static:nonroot + +WORKDIR / + +COPY push/bin/push push + +COPY images images + +USER 65532:65532 diff --git a/hack/test/image-registry.sh b/testdata/build-test-registry.sh similarity index 60% rename from hack/test/image-registry.sh rename to testdata/build-test-registry.sh index bbfa096a82..e2dcc09148 100755 --- a/hack/test/image-registry.sh +++ b/testdata/build-test-registry.sh @@ -1,20 +1,21 @@ -#! /bin/bash +#!/bin/bash set -o errexit set -o nounset set -o pipefail help=" -image-registry.sh is a script to stand up an image registry within a cluster. +build-test-registry.sh is a script to stand up an image registry within a cluster. Usage: - image-registry.sh [NAMESPACE] [NAME] + build-test-registry.sh [NAMESPACE] [NAME] [IMAGE] Argument Descriptions: - NAMESPACE is the namespace that should be created and is the namespace in which the image registry will be created - NAME is the name that should be used for the image registry Deployment and Service + - IMAGE is the name of the image that should be used to run the image registry " -if [[ "$#" -ne 2 ]]; then +if [[ "$#" -ne 3 ]]; then echo "Illegal number of arguments passed" echo "${help}" exit 1 @@ -22,6 +23,7 @@ fi namespace=$1 name=$2 +image=$3 kubectl apply -f - << EOF apiVersion: v1 @@ -40,7 +42,10 @@ spec: dnsNames: - ${name}.${namespace}.svc - ${name}.${namespace}.svc.cluster.local + - ${name}-controller-manager-metrics-service.${namespace}.svc + - ${name}-controller-manager-metrics-service.${namespace}.svc.cluster.local privateKey: + rotationPolicy: Always algorithm: ECDSA size: 256 issuerRef: @@ -67,7 +72,8 @@ spec: spec: containers: - name: registry - image: registry:2 + image: registry:3 + imagePullPolicy: IfNotPresent volumeMounts: - name: certs-vol mountPath: "/certs" @@ -98,3 +104,35 @@ spec: EOF kubectl wait --for=condition=Available -n "${namespace}" "deploy/${name}" --timeout=60s + +kubectl apply -f - << EOF +apiVersion: batch/v1 +kind: Job +metadata: + name: ${name}-push + namespace: "${namespace}" +spec: + template: + spec: + restartPolicy: Never + containers: + - name: push + image: ${image} + command: + - /push + args: + - "--registry-address=${name}.${namespace}.svc:5000" + - "--images-path=/images" + volumeMounts: + - name: certs-vol + mountPath: "/certs" + env: + - name: SSL_CERT_DIR + value: "/certs/" + volumes: + - name: certs-vol + secret: + secretName: ${namespace}-registry +EOF + +kubectl wait --for=condition=Complete -n "${namespace}" "job/${name}-push" --timeout=60s diff --git a/testdata/bundles/registry-v1/build-push-e2e-bundle.sh b/testdata/bundles/registry-v1/build-push-e2e-bundle.sh deleted file mode 100755 index 0aec13cc9f..0000000000 --- a/testdata/bundles/registry-v1/build-push-e2e-bundle.sh +++ /dev/null @@ -1,84 +0,0 @@ -#! /bin/bash - -set -o errexit -set -o nounset -set -o pipefail - -help=" -build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. -Usage: - build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_NAME] [BUNDLE_DIR] - -Argument Descriptions: - - NAMESPACE is the namespace the kaniko Job should be created in - - TAG is the full tag used to build and push the catalog image -" - -if [[ "$#" -ne 4 ]]; then - echo "Illegal number of arguments passed" - echo "${help}" - exit 1 -fi - - -namespace=$1 -tag=$2 -bundle_name=$3 -package_name=$4 -bundle_dir="testdata/bundles/registry-v1/${package_name}" - -echo "${namespace}" "${tag}" - -kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/Dockerfile" operator-controller-e2e-${bundle_name}.root - -tgz="${bundle_dir}/manifests.tgz" -tar czf "${tgz}" -C "${bundle_dir}/" manifests metadata -kubectl create configmap -n "${namespace}" --from-file="${tgz}" operator-controller-${bundle_name}.manifests -rm "${tgz}" - -# Remove periods from bundle name due to pod name issues -job_name=${bundle_name//.} - -kubectl apply -f - << EOF -apiVersion: batch/v1 -kind: Job -metadata: - name: "kaniko-${job_name}" - namespace: "${namespace}" -spec: - template: - spec: - initContainers: - - name: copy-manifests - image: busybox - command: ['sh', '-c', 'cp /manifests-data/* /manifests'] - volumeMounts: - - name: manifests - mountPath: /manifests - - name: manifests-data - mountPath: /manifests-data - containers: - - name: kaniko - image: gcr.io/kaniko-project/executor:latest - args: ["--dockerfile=/workspace/Dockerfile", - "--context=tar:///workspace/manifests/manifests.tgz", - "--destination=${tag}", - "--skip-tls-verify"] - volumeMounts: - - name: dockerfile - mountPath: /workspace/ - - name: manifests - mountPath: /workspace/manifests/ - restartPolicy: Never - volumes: - - name: dockerfile - configMap: - name: operator-controller-e2e-${bundle_name}.root - - name: manifests - emptyDir: {} - - name: manifests-data - configMap: - name: operator-controller-${bundle_name}.manifests -EOF - -kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${job_name} --timeout=60s diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/Dockerfile b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/Dockerfile deleted file mode 100644 index 5a14581489..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM scratch - -# Core bundle labels. -LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 -LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ -LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ -LABEL operators.operatorframework.io.bundle.package.v1=prometheusoperator -LABEL operators.operatorframework.io.bundle.channels.v1=beta -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.28.0 -LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 -LABEL operators.operatorframework.io.metrics.project_layout=unknown - -# Copy files to locations specified by labels. -COPY manifests /manifests/ -COPY metadata /metadata/ diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagerconfigs.yaml b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagerconfigs.yaml deleted file mode 100644 index 64919a78a1..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagerconfigs.yaml +++ /dev/null @@ -1,4485 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - name: alertmanagerconfigs.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - categories: - - prometheus-operator - kind: AlertmanagerConfig - listKind: AlertmanagerConfigList - plural: alertmanagerconfigs - shortNames: - - amcfg - singular: alertmanagerconfig - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: AlertmanagerConfig defines a namespaced AlertmanagerConfig to - be aggregated across multiple namespaces configuring one Alertmanager cluster. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: AlertmanagerConfigSpec is a specification of the desired - behavior of the Alertmanager configuration. By definition, the Alertmanager - configuration only applies to alerts for which the `namespace` label - is equal to the namespace of the AlertmanagerConfig resource. - properties: - inhibitRules: - description: List of inhibition rules. The rules will only apply to - alerts matching the resource's namespace. - items: - description: InhibitRule defines an inhibition rule that allows - to mute alerts when other alerts are already firing. See https://prometheus.io/docs/alerting/latest/configuration/#inhibit_rule - properties: - equal: - description: Labels that must have an equal value in the source - and target alert for the inhibition to take effect. - items: - type: string - type: array - sourceMatch: - description: Matchers for which one or more alerts have to exist - for the inhibition to take effect. The operator enforces that - the alert matches the resource's namespace. - items: - description: Matcher defines how to match on alert's labels. - properties: - matchType: - description: Match operation available with AlertManager - >= v0.22.0 and takes precedence over Regex (deprecated) - if non-empty. - enum: - - '!=' - - = - - =~ - - '!~' - type: string - name: - description: Label to match. - minLength: 1 - type: string - regex: - description: Whether to match on equality (false) or regular-expression - (true). Deprecated as of AlertManager >= v0.22.0 where - a user should use MatchType instead. - type: boolean - value: - description: Label value to match. - type: string - required: - - name - type: object - type: array - targetMatch: - description: Matchers that have to be fulfilled in the alerts - to be muted. The operator enforces that the alert matches - the resource's namespace. - items: - description: Matcher defines how to match on alert's labels. - properties: - matchType: - description: Match operation available with AlertManager - >= v0.22.0 and takes precedence over Regex (deprecated) - if non-empty. - enum: - - '!=' - - = - - =~ - - '!~' - type: string - name: - description: Label to match. - minLength: 1 - type: string - regex: - description: Whether to match on equality (false) or regular-expression - (true). Deprecated as of AlertManager >= v0.22.0 where - a user should use MatchType instead. - type: boolean - value: - description: Label value to match. - type: string - required: - - name - type: object - type: array - type: object - type: array - muteTimeIntervals: - description: List of MuteTimeInterval specifying when the routes should - be muted. - items: - description: MuteTimeInterval specifies the periods in time when - notifications will be muted - properties: - name: - description: Name of the time interval - type: string - timeIntervals: - description: TimeIntervals is a list of TimeInterval - items: - description: TimeInterval describes intervals of time - properties: - daysOfMonth: - description: DaysOfMonth is a list of DayOfMonthRange - items: - description: DayOfMonthRange is an inclusive range of - days of the month beginning at 1 - properties: - end: - description: End of the inclusive range - maximum: 31 - minimum: -31 - type: integer - start: - description: Start of the inclusive range - maximum: 31 - minimum: -31 - type: integer - type: object - type: array - months: - description: Months is a list of MonthRange - items: - description: MonthRange is an inclusive range of months - of the year beginning in January Months can be specified - by name (e.g 'January') by numerical month (e.g '1') - or as an inclusive range (e.g 'January:March', '1:3', - '1:March') - pattern: ^((?i)january|february|march|april|may|june|july|august|september|october|november|december|[1-12])(?:((:((?i)january|february|march|april|may|june|july|august|september|october|november|december|[1-12]))$)|$) - type: string - type: array - times: - description: Times is a list of TimeRange - items: - description: TimeRange defines a start and end time - in 24hr format - properties: - endTime: - description: EndTime is the end time in 24hr format. - pattern: ^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$) - type: string - startTime: - description: StartTime is the start time in 24hr - format. - pattern: ^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$) - type: string - type: object - type: array - weekdays: - description: Weekdays is a list of WeekdayRange - items: - description: WeekdayRange is an inclusive range of days - of the week beginning on Sunday Days can be specified - by name (e.g 'Sunday') or as an inclusive range (e.g - 'Monday:Friday') - pattern: ^((?i)sun|mon|tues|wednes|thurs|fri|satur)day(?:((:(sun|mon|tues|wednes|thurs|fri|satur)day)$)|$) - type: string - type: array - years: - description: Years is a list of YearRange - items: - description: YearRange is an inclusive range of years - pattern: ^2\d{3}(?::2\d{3}|$) - type: string - type: array - type: object - type: array - type: object - type: array - receivers: - description: List of receivers. - items: - description: Receiver defines one or more notification integrations. - properties: - emailConfigs: - description: List of Email configurations. - items: - description: EmailConfig configures notifications via Email. - properties: - authIdentity: - description: The identity to use for authentication. - type: string - authPassword: - description: The secret's key that contains the password - to use for authentication. The secret needs to be in - the same namespace as the AlertmanagerConfig object - and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - authSecret: - description: The secret's key that contains the CRAM-MD5 - secret. The secret needs to be in the same namespace - as the AlertmanagerConfig object and accessible by the - Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - authUsername: - description: The username to use for authentication. - type: string - from: - description: The sender address. - type: string - headers: - description: Further headers email header key/value pairs. - Overrides any headers previously set by the notification - implementation. - items: - description: KeyValue defines a (key, value) tuple. - properties: - key: - description: Key of the tuple. - minLength: 1 - type: string - value: - description: Value of the tuple. - type: string - required: - - key - - value - type: object - type: array - hello: - description: The hostname to identify to the SMTP server. - type: string - html: - description: The HTML body of the email notification. - type: string - requireTLS: - description: The SMTP TLS requirement. Note that Go does - not support unencrypted connections to remote SMTP endpoints. - type: boolean - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - smarthost: - description: The SMTP host and port through which emails - are sent. E.g. example.com:25 - type: string - text: - description: The text body of the email notification. - type: string - tlsConfig: - description: TLS configuration - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to use - for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for - the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when doing - client-authentication. - properties: - configMap: - description: ConfigMap containing data to use - for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for - the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key file - for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the targets. - type: string - type: object - to: - description: The email address to send notifications to. - type: string - type: object - type: array - name: - description: Name of the receiver. Must be unique across all - items from the list. - minLength: 1 - type: string - opsgenieConfigs: - description: List of OpsGenie configurations. - items: - description: OpsGenieConfig configures notifications via OpsGenie. - See https://prometheus.io/docs/alerting/latest/configuration/#opsgenie_config - properties: - actions: - description: Comma separated list of actions that will - be available for the alert. - type: string - apiKey: - description: The secret's key that contains the OpsGenie - API key. The secret needs to be in the same namespace - as the AlertmanagerConfig object and accessible by the - Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - apiURL: - description: The URL to send OpsGenie API requests to. - type: string - description: - description: Description of the incident. - type: string - details: - description: A set of arbitrary key/value pairs that provide - further detail about the incident. - items: - description: KeyValue defines a (key, value) tuple. - properties: - key: - description: Key of the tuple. - minLength: 1 - type: string - value: - description: Value of the tuple. - type: string - required: - - key - - value - type: object - type: array - entity: - description: Optional field that can be used to specify - which domain alert is related to. - type: string - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - message: - description: Alert text limited to 130 characters. - type: string - note: - description: Additional alert note. - type: string - priority: - description: Priority level of alert. Possible values - are P1, P2, P3, P4, and P5. - type: string - responders: - description: List of responders responsible for notifications. - items: - description: OpsGenieConfigResponder defines a responder - to an incident. One of `id`, `name` or `username` - has to be defined. - properties: - id: - description: ID of the responder. - type: string - name: - description: Name of the responder. - type: string - type: - description: Type of responder. - enum: - - team - - teams - - user - - escalation - - schedule - minLength: 1 - type: string - username: - description: Username of the responder. - type: string - required: - - type - type: object - type: array - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - source: - description: Backlink to the sender of the notification. - type: string - tags: - description: Comma separated list of tags attached to - the notifications. - type: string - updateAlerts: - description: Whether to update message and description - of the alert in OpsGenie if it already exists By default, - the alert is never updated in OpsGenie, the new message - only appears in activity log. - type: boolean - type: object - type: array - pagerdutyConfigs: - description: List of PagerDuty configurations. - items: - description: PagerDutyConfig configures notifications via - PagerDuty. See https://prometheus.io/docs/alerting/latest/configuration/#pagerduty_config - properties: - class: - description: The class/type of the event. - type: string - client: - description: Client identification. - type: string - clientURL: - description: Backlink to the sender of notification. - type: string - component: - description: The part or component of the affected system - that is broken. - type: string - description: - description: Description of the incident. - type: string - details: - description: Arbitrary key/value pairs that provide further - detail about the incident. - items: - description: KeyValue defines a (key, value) tuple. - properties: - key: - description: Key of the tuple. - minLength: 1 - type: string - value: - description: Value of the tuple. - type: string - required: - - key - - value - type: object - type: array - group: - description: A cluster or grouping of sources. - type: string - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - pagerDutyImageConfigs: - description: A list of image details to attach that provide - further detail about an incident. - items: - description: PagerDutyImageConfig attaches images to - an incident - properties: - alt: - description: Alt is the optional alternative text - for the image. - type: string - href: - description: Optional URL; makes the image a clickable - link. - type: string - src: - description: Src of the image being attached to - the incident - type: string - type: object - type: array - pagerDutyLinkConfigs: - description: A list of link details to attach that provide - further detail about an incident. - items: - description: PagerDutyLinkConfig attaches text links - to an incident - properties: - alt: - description: Text that describes the purpose of - the link, and can be used as the link's text. - type: string - href: - description: Href is the URL of the link to be attached - type: string - type: object - type: array - routingKey: - description: The secret's key that contains the PagerDuty - integration key (when using Events API v2). Either this - field or `serviceKey` needs to be defined. The secret - needs to be in the same namespace as the AlertmanagerConfig - object and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - serviceKey: - description: The secret's key that contains the PagerDuty - service key (when using integration type "Prometheus"). - Either this field or `routingKey` needs to be defined. - The secret needs to be in the same namespace as the - AlertmanagerConfig object and accessible by the Prometheus - Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - severity: - description: Severity of the incident. - type: string - url: - description: The URL to send requests to. - type: string - type: object - type: array - pushoverConfigs: - description: List of Pushover configurations. - items: - description: PushoverConfig configures notifications via Pushover. - See https://prometheus.io/docs/alerting/latest/configuration/#pushover_config - properties: - expire: - description: How long your notification will continue - to be retried for, unless the user acknowledges the - notification. - pattern: ^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$ - type: string - html: - description: Whether notification message is HTML or plain - text. - type: boolean - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - message: - description: Notification message. - type: string - priority: - description: Priority, see https://pushover.net/api#priority - type: string - retry: - description: How often the Pushover servers will send - the same notification to the user. Must be at least - 30 seconds. - pattern: ^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$ - type: string - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - sound: - description: The name of one of the sounds supported by - device clients to override the user's default sound - choice - type: string - title: - description: Notification title. - type: string - token: - description: The secret's key that contains the registered - application's API token, see https://pushover.net/apps. - The secret needs to be in the same namespace as the - AlertmanagerConfig object and accessible by the Prometheus - Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - url: - description: A supplementary URL shown alongside the message. - type: string - urlTitle: - description: A title for supplementary URL, otherwise - just the URL is shown - type: string - userKey: - description: The secret's key that contains the recipient - user's user key. The secret needs to be in the same - namespace as the AlertmanagerConfig object and accessible - by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - type: object - type: array - slackConfigs: - description: List of Slack configurations. - items: - description: SlackConfig configures notifications via Slack. - See https://prometheus.io/docs/alerting/latest/configuration/#slack_config - properties: - actions: - description: A list of Slack actions that are sent with - each notification. - items: - description: SlackAction configures a single Slack action - that is sent with each notification. See https://api.slack.com/docs/message-attachments#action_fields - and https://api.slack.com/docs/message-buttons for - more information. - properties: - confirm: - description: SlackConfirmationField protect users - from destructive actions or particularly distinguished - decisions by asking them to confirm their button - click one more time. See https://api.slack.com/docs/interactive-message-field-guide#confirmation_fields - for more information. - properties: - dismissText: - type: string - okText: - type: string - text: - minLength: 1 - type: string - title: - type: string - required: - - text - type: object - name: - type: string - style: - type: string - text: - minLength: 1 - type: string - type: - minLength: 1 - type: string - url: - type: string - value: - type: string - required: - - text - - type - type: object - type: array - apiURL: - description: The secret's key that contains the Slack - webhook URL. The secret needs to be in the same namespace - as the AlertmanagerConfig object and accessible by the - Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - callbackId: - type: string - channel: - description: The channel or user to send notifications - to. - type: string - color: - type: string - fallback: - type: string - fields: - description: A list of Slack fields that are sent with - each notification. - items: - description: SlackField configures a single Slack field - that is sent with each notification. Each field must - contain a title, value, and optionally, a boolean - value to indicate if the field is short enough to - be displayed next to other fields designated as short. - See https://api.slack.com/docs/message-attachments#fields - for more information. - properties: - short: - type: boolean - title: - minLength: 1 - type: string - value: - minLength: 1 - type: string - required: - - title - - value - type: object - type: array - footer: - type: string - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - iconEmoji: - type: string - iconURL: - type: string - imageURL: - type: string - linkNames: - type: boolean - mrkdwnIn: - items: - type: string - type: array - pretext: - type: string - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - shortFields: - type: boolean - text: - type: string - thumbURL: - type: string - title: - type: string - titleLink: - type: string - username: - type: string - type: object - type: array - snsConfigs: - description: List of SNS configurations - items: - description: SNSConfig configures notifications via AWS SNS. - See https://prometheus.io/docs/alerting/latest/configuration/#sns_configs - properties: - apiURL: - description: The SNS API URL i.e. https://sns.us-east-2.amazonaws.com. - If not specified, the SNS API URL from the SNS SDK will - be used. - type: string - attributes: - additionalProperties: - type: string - description: SNS message attributes. - type: object - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - message: - description: The message content of the SNS notification. - type: string - phoneNumber: - description: Phone number if message is delivered via - SMS in E.164 format. If you don't specify this value, - you must specify a value for the TopicARN or TargetARN. - type: string - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - sigv4: - description: Configures AWS's Signature Verification 4 - signing process to sign requests. - properties: - accessKey: - description: AccessKey is the AWS API key. If blank, - the environment variable `AWS_ACCESS_KEY_ID` is - used. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - profile: - description: Profile is the named AWS profile used - to authenticate. - type: string - region: - description: Region is the AWS region. If blank, the - region from the default credentials chain used. - type: string - roleArn: - description: RoleArn is the named AWS profile used - to authenticate. - type: string - secretKey: - description: SecretKey is the AWS API secret. If blank, - the environment variable `AWS_SECRET_ACCESS_KEY` - is used. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - subject: - description: Subject line when the message is delivered - to email endpoints. - type: string - targetARN: - description: The mobile platform endpoint ARN if message - is delivered via mobile notifications. If you don't - specify this value, you must specify a value for the - topic_arn or PhoneNumber. - type: string - topicARN: - description: SNS topic ARN, i.e. arn:aws:sns:us-east-2:698519295917:My-Topic - If you don't specify this value, you must specify a - value for the PhoneNumber or TargetARN. - type: string - type: object - type: array - telegramConfigs: - description: List of Telegram configurations. - items: - description: TelegramConfig configures notifications via Telegram. - See https://prometheus.io/docs/alerting/latest/configuration/#telegram_config - properties: - apiURL: - description: The Telegram API URL i.e. https://api.telegram.org. - If not specified, default API URL will be used. - type: string - botToken: - description: Telegram bot token The secret needs to be - in the same namespace as the AlertmanagerConfig object - and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - chatID: - description: The Telegram chat ID. - format: int64 - type: integer - disableNotifications: - description: Disable telegram notifications - type: boolean - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - message: - description: Message template - type: string - parseMode: - description: Parse mode for telegram message - enum: - - MarkdownV2 - - Markdown - - HTML - type: string - sendResolved: - description: Whether to notify about resolved alerts. - type: boolean - type: object - type: array - victoropsConfigs: - description: List of VictorOps configurations. - items: - description: VictorOpsConfig configures notifications via - VictorOps. See https://prometheus.io/docs/alerting/latest/configuration/#victorops_config - properties: - apiKey: - description: The secret's key that contains the API key - to use when talking to the VictorOps API. The secret - needs to be in the same namespace as the AlertmanagerConfig - object and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - apiUrl: - description: The VictorOps API URL. - type: string - customFields: - description: Additional custom fields for notification. - items: - description: KeyValue defines a (key, value) tuple. - properties: - key: - description: Key of the tuple. - minLength: 1 - type: string - value: - description: Value of the tuple. - type: string - required: - - key - - value - type: object - type: array - entityDisplayName: - description: Contains summary of the alerted problem. - type: string - httpConfig: - description: The HTTP client's configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - messageType: - description: Describes the behavior of the alert (CRITICAL, - WARNING, INFO). - type: string - monitoringTool: - description: The monitoring tool the state message is - from. - type: string - routingKey: - description: A key used to map the alert to a team. - type: string - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - stateMessage: - description: Contains long explanation of the alerted - problem. - type: string - type: object - type: array - webhookConfigs: - description: List of webhook configurations. - items: - description: WebhookConfig configures notifications via a - generic receiver supporting the webhook payload. See https://prometheus.io/docs/alerting/latest/configuration/#webhook_config - properties: - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - maxAlerts: - description: Maximum number of alerts to be sent per webhook - message. When 0, all alerts are included. - format: int32 - minimum: 0 - type: integer - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - url: - description: The URL to send HTTP POST requests to. `urlSecret` - takes precedence over `url`. One of `urlSecret` and - `url` should be defined. - type: string - urlSecret: - description: The secret's key that contains the webhook - URL to send HTTP requests to. `urlSecret` takes precedence - over `url`. One of `urlSecret` and `url` should be defined. - The secret needs to be in the same namespace as the - AlertmanagerConfig object and accessible by the Prometheus - Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - type: object - type: array - wechatConfigs: - description: List of WeChat configurations. - items: - description: WeChatConfig configures notifications via WeChat. - See https://prometheus.io/docs/alerting/latest/configuration/#wechat_config - properties: - agentID: - type: string - apiSecret: - description: The secret's key that contains the WeChat - API key. The secret needs to be in the same namespace - as the AlertmanagerConfig object and accessible by the - Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - apiURL: - description: The WeChat API URL. - type: string - corpID: - description: The corp id for authentication. - type: string - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for - the client. This is mutually exclusive with BasicAuth - and is only available starting from Alertmanager - v0.22+. - properties: - credentials: - description: The secret's key that contains the - credentials of the request - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, - BasicAuth takes precedence. - properties: - password: - description: The secret in the service monitor - namespace that contains the password for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor - namespace that contains the username for authentication. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. - The secret needs to be in the same namespace as - the AlertmanagerConfig object and accessible by - the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - followRedirects: - description: FollowRedirects specifies whether the - client should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch - a token for the targets. - properties: - clientId: - description: The secret or configmap containing - the OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 - client secret - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token - URL - type: object - scopes: - description: OAuth2 scopes used for the token - request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when - doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to - use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use - for the targets. - properties: - key: - description: The key of the secret to - select from. Must be a valid secret - key. - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key - file for the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the - targets. - type: string - type: object - type: object - message: - description: API request data as defined by the WeChat - API. - type: string - messageType: - type: string - sendResolved: - description: Whether or not to notify about resolved alerts. - type: boolean - toParty: - type: string - toTag: - type: string - toUser: - type: string - type: object - type: array - required: - - name - type: object - type: array - route: - description: The Alertmanager route definition for alerts matching - the resource's namespace. If present, it will be added to the generated - Alertmanager configuration as a first-level route. - properties: - activeTimeIntervals: - description: ActiveTimeIntervals is a list of MuteTimeInterval - names when this route should be active. - items: - type: string - type: array - continue: - description: Boolean indicating whether an alert should continue - matching subsequent sibling nodes. It will always be overridden - to true for the first-level route by the Prometheus operator. - type: boolean - groupBy: - description: List of labels to group by. Labels must not be repeated - (unique list). Special label "..." (aggregate by all possible - labels), if provided, must be the only element in the list. - items: - type: string - type: array - groupInterval: - description: 'How long to wait before sending an updated notification. - Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$` - Example: "5m"' - type: string - groupWait: - description: 'How long to wait before sending the initial notification. - Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$` - Example: "30s"' - type: string - matchers: - description: 'List of matchers that the alert''s labels should - match. For the first level route, the operator removes any existing - equality and regexp matcher on the `namespace` label and adds - a `namespace: ` matcher.' - items: - description: Matcher defines how to match on alert's labels. - properties: - matchType: - description: 'Match operation available with AlertManager - >= v0.22.0 and takes precedence over Regex (deprecated) - if non-empty.' - enum: - - '!=' - - = - - =~ - - '!~' - type: string - name: - description: Label to match. - minLength: 1 - type: string - regex: - description: Whether to match on equality (false) or regular-expression - (true). Deprecated as of AlertManager >= v0.22.0 where - a user should use MatchType instead. - type: boolean - value: - description: Label value to match. - type: string - required: - - name - type: object - type: array - muteTimeIntervals: - description: 'Note: this comment applies to the field definition - above but appears below otherwise it gets included in the generated - manifest. CRD schema doesn''t support self-referential types - for now (see https://github.com/kubernetes/kubernetes/issues/62872). - We have to use an alternative type to circumvent the limitation. - The downside is that the Kube API can''t validate the data beyond - the fact that it is a valid JSON representation. MuteTimeIntervals - is a list of MuteTimeInterval names that will mute this route - when matched,' - items: - type: string - type: array - receiver: - description: Name of the receiver for this route. If not empty, - it should be listed in the `receivers` field. - type: string - repeatInterval: - description: 'How long to wait before repeating the last notification. - Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$` - Example: "4h"' - type: string - routes: - description: Child routes. - items: - x-kubernetes-preserve-unknown-fields: true - type: array - type: object - type: object - required: - - spec - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagers.yaml b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagers.yaml deleted file mode 100644 index 526ad188fb..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_alertmanagers.yaml +++ /dev/null @@ -1,7227 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - name: alertmanagers.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - categories: - - prometheus-operator - kind: Alertmanager - listKind: AlertmanagerList - plural: alertmanagers - shortNames: - - am - singular: alertmanager - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The version of Alertmanager - jsonPath: .spec.version - name: Version - type: string - - description: The number of desired replicas - jsonPath: .spec.replicas - name: Replicas - type: integer - - description: The number of ready replicas - jsonPath: .status.availableReplicas - name: Ready - type: integer - - jsonPath: .status.conditions[?(@.type == 'Reconciled')].status - name: Reconciled - type: string - - jsonPath: .status.conditions[?(@.type == 'Available')].status - name: Available - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - description: Whether the resource reconciliation is paused or not - jsonPath: .status.paused - name: Paused - priority: 1 - type: boolean - name: v1 - schema: - openAPIV3Schema: - description: Alertmanager describes an Alertmanager cluster. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: 'Specification of the desired behavior of the Alertmanager - cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' - properties: - additionalPeers: - description: AdditionalPeers allows injecting a set of additional - Alertmanagers to peer with to form a highly available cluster. - items: - type: string - type: array - affinity: - description: If specified, the pod's scheduling constraints. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the - pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the affinity expressions specified by - this field, but it may choose a node that violates one or - more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node matches - the corresponding matchExpressions; the node(s) with the - highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches - all objects with implicit weight 0 (i.e. it's a no-op). - A null preferred scheduling term matches no objects (i.e. - is also a no-op). - properties: - preference: - description: A node selector term, associated with the - corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to an update), the system may or may not try to - eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate - this pod in the same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the affinity expressions specified by - this field, but it may choose a node that violates one or - more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node has - pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may - not try to eventually evict the pod from its node. When - there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms - must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of - pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied to the - union of the namespaces selected by this field and - the ones listed in the namespaces field. null selector - and null or empty namespaces list means "this pod's - namespace". An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace - names that the term applies to. The term is applied - to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. null or - empty namespaces list and null namespaceSelector means - "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of - any node on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. - avoid putting this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the anti-affinity expressions specified - by this field, but it may choose a node that violates one - or more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node has - pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its - node. When there are multiple elements, the lists of nodes - corresponding to each podAffinityTerm are intersected, i.e. - all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of - pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied to the - union of the namespaces selected by this field and - the ones listed in the namespaces field. null selector - and null or empty namespaces list means "this pod's - namespace". An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace - names that the term applies to. The term is applied - to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. null or - empty namespaces list and null namespaceSelector means - "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of - any node on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - alertmanagerConfigMatcherStrategy: - description: The AlertmanagerConfigMatcherStrategy defines how AlertmanagerConfig - objects match the alerts. In the future more options may be added. - properties: - type: - default: OnNamespace - description: If set to `OnNamespace`, the operator injects a label - matcher matching the namespace of the AlertmanagerConfig object - for all its routes and inhibition rules. `None` will not add - any additional matchers other than the ones specified in the - AlertmanagerConfig. Default is `OnNamespace`. - enum: - - OnNamespace - - None - type: string - type: object - alertmanagerConfigNamespaceSelector: - description: Namespaces to be selected for AlertmanagerConfig discovery. - If nil, only check own namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - alertmanagerConfigSelector: - description: AlertmanagerConfigs to be selected for to merge and configure - Alertmanager with. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - alertmanagerConfiguration: - description: 'EXPERIMENTAL: alertmanagerConfiguration specifies the - configuration of Alertmanager. If defined, it takes precedence over - the `configSecret` field. This field may change in future releases.' - properties: - global: - description: Defines the global parameters of the Alertmanager - configuration. - properties: - httpConfig: - description: HTTP client configuration. - properties: - authorization: - description: Authorization header configuration for the - client. This is mutually exclusive with BasicAuth and - is only available starting from Alertmanager v0.22+. - properties: - credentials: - description: The secret's key that contains the credentials - of the request - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults - to Bearer, Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth for the client. This is mutually - exclusive with Authorization. If both are defined, BasicAuth - takes precedence. - properties: - password: - description: The secret in the service monitor namespace - that contains the password for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor namespace - that contains the username for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: The secret's key that contains the bearer - token to be used by the client for authentication. The - secret needs to be in the same namespace as the Alertmanager - object and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - followRedirects: - description: FollowRedirects specifies whether the client - should follow HTTP 3xx redirects. - type: boolean - oauth2: - description: OAuth2 client credentials used to fetch a - token for the targets. - properties: - clientId: - description: The secret or configmap containing the - OAuth2 client id - properties: - configMap: - description: ConfigMap containing data to use - for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for - the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 client - secret - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token URL - type: object - scopes: - description: OAuth2 scopes used for the token request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - proxyURL: - description: Optional proxy URL. - type: string - tlsConfig: - description: TLS configuration for the client. - properties: - ca: - description: Certificate authority used when verifying - server certificates. - properties: - configMap: - description: ConfigMap containing data to use - for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for - the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when doing - client-authentication. - properties: - configMap: - description: ConfigMap containing data to use - for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap - or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for - the targets. - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key file - for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the targets. - type: string - type: object - type: object - opsGenieApiKey: - description: The default OpsGenie API Key. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - opsGenieApiUrl: - description: The default OpsGenie API URL. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - resolveTimeout: - description: ResolveTimeout is the default value used by alertmanager - if the alert does not include EndsAt, after this time passes - it can declare the alert as resolved if it has not been - updated. This has no impact on alerts from Prometheus, as - they always include EndsAt. - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - slackApiUrl: - description: The default Slack API URL. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - name: - description: The name of the AlertmanagerConfig resource which - is used to generate the Alertmanager configuration. It must - be defined in the same namespace as the Alertmanager object. - The operator will not enforce a `namespace` label for routes - and inhibition rules. - minLength: 1 - type: string - templates: - description: Custom notification templates. - items: - description: SecretOrConfigMap allows to specify data as a Secret - or ConfigMap. Fields are mutually exclusive. - properties: - configMap: - description: ConfigMap containing data to use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - baseImage: - description: 'Base image that is used to deploy pods, without tag. - Deprecated: use ''image'' instead' - type: string - clusterAdvertiseAddress: - description: 'ClusterAdvertiseAddress is the explicit address to advertise - in cluster. Needs to be provided for non RFC1918 [1] (public) addresses. - [1] RFC1918: https://tools.ietf.org/html/rfc1918' - type: string - clusterGossipInterval: - description: Interval between gossip attempts. - pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - clusterPeerTimeout: - description: Timeout for cluster peering. - pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - clusterPushpullInterval: - description: Interval between pushpull attempts. - pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - configMaps: - description: ConfigMaps is a list of ConfigMaps in the same namespace - as the Alertmanager object, which shall be mounted into the Alertmanager - Pods. Each ConfigMap is added to the StatefulSet definition as a - volume named `configmap-`. The ConfigMaps are mounted - into `/etc/alertmanager/configmaps/` in the 'alertmanager' - container. - items: - type: string - type: array - configSecret: - description: "ConfigSecret is the name of a Kubernetes Secret in the - same namespace as the Alertmanager object, which contains the configuration - for this Alertmanager instance. If empty, it defaults to `alertmanager-`. - \n The Alertmanager configuration should be available under the - `alertmanager.yaml` key. Additional keys from the original secret - are copied to the generated secret and mounted into the `/etc/alertmanager/config` - directory in the `alertmanager` container. \n If either the secret - or the `alertmanager.yaml` key is missing, the operator provisions - a minimal Alertmanager configuration with one empty receiver (effectively - dropping alert notifications)." - type: string - containers: - description: 'Containers allows injecting additional containers. This - is meant to allow adding an authentication proxy to an Alertmanager - pod. Containers described here modify an operator generated container - if they share the same name and modifications are done via a strategic - merge patch. The current container names are: `alertmanager` and - `config-reloader`. Overriding containers is entirely outside the - scope of what the maintainers will support and by doing so, you - accept that this behaviour may break at any time without notice.' - items: - description: A single application container that you want to run - within a pod. - properties: - args: - description: 'Arguments to the entrypoint. The container image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will - be unchanged. Double $$ are reduced to a single $, which allows - for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will - produce the string literal "$(VAR_NAME)". Escaped references - will never be expanded, regardless of whether the variable - exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. - The container image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: - i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether - the variable exists or not. Cannot be updated. More info: - https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in - the container and any service environment variables. - If a variable cannot be resolved, the reference in the - input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) - syntax: i.e. "$$(VAR_NAME)" will produce the string - literal "$(VAR_NAME)". Escaped references will never - be expanded, regardless of whether the variable exists - or not. Defaults to "".' - type: string - valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. - properties: - configMapKeyRef: - description: Selects a key of a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format of the - exposed resources, defaults to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - description: Selects a key of a secret in the pod's - namespace - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be - a C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key - will take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set - of ConfigMaps - properties: - configMapRef: - description: The ConfigMap to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap must be - defined - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: The Secret to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - image: - description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Actions that the management system should take - in response to container lifecycle events. Cannot be updated. - properties: - postStart: - description: 'PostStart is called immediately after a container - is created. If the handler fails, the container is terminated - and restarted according to its restart policy. Other management - of the container blocks until the hook completes. More - info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - description: 'PreStop is called immediately before a container - is terminated due to an API request or management event - such as liveness/startup probe failure, preemption, resource - contention, etc. The handler is not called if the container - crashes or exits. The Pod''s termination grace period - countdown begins before the PreStop hook is executed. - Regardless of the outcome of the handler, the container - will eventually terminate within the Pod''s termination - grace period (unless delayed by finalizers). Other management - of the container blocks until the hook completes or until - the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - description: 'Periodic probe of container liveness. Container - will be restarted if the probe fails. Cannot be updated. More - info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - name: - description: Name of the container specified as a DNS_LABEL. - Each container in a pod must have a unique name (DNS_LABEL). - Cannot be updated. - type: string - ports: - description: List of ports to expose from the container. Not - specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Modifying this array with strategic merge patch may corrupt - the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. - Cannot be updated. - items: - description: ContainerPort represents a network port in a - single container. - properties: - containerPort: - description: Number of port to expose on the pod's IP - address. This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If - specified, this must be a valid port number, 0 < x < - 65536. If HostNetwork is specified, this must match - ContainerPort. Most containers do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod - must have a unique name. Name for the port that can - be referred to by services. - type: string - protocol: - default: TCP - description: Protocol for port. Must be UDP, TCP, or SCTP. - Defaults to "TCP". - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - description: 'Periodic probe of container service readiness. - Container will be removed from service endpoints if the probe - fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - resizePolicy: - description: Resources resize policy for the container. - items: - description: ContainerResizePolicy represents resource resize - policy for the container. - properties: - resourceName: - description: 'Name of the resource to which this resource - resize policy applies. Supported values: cpu, memory.' - type: string - restartPolicy: - description: Restart policy to apply when specified resource - is resized. If not specified, it defaults to NotRequired. - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - description: 'Compute Resources required by this container. - Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - securityContext: - description: 'SecurityContext defines the security options the - container should be run with. If set, the fields of SecurityContext - override the equivalent fields of PodSecurityContext. More - info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by - the container runtime. Note that this field cannot be - set when spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent to - root on the host. Defaults to false. Note that this field - cannot be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to - use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. Note that this field cannot - be set when spec.os.name is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a - non-root user. If true, the Kubelet will validate the - image at runtime to ensure that it does not run as UID - 0 (root) and fail to start the container if it does. If - unset or false, no such validation will be performed. - May also be set in PodSecurityContext. If set in both - SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a - random SELinux context for each container. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. - Note that this field cannot be set when spec.os.name is - windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile - must be preconfigured on the node to work. Must be - a descending path, relative to the kubelet's configured - seccomp profile location. Must only be set if type - is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should be - used. RuntimeDefault - the container runtime default - profile should be used. Unconfined - no profile should - be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components - that enable the WindowsHostProcessContainers feature - flag. Setting this field without the feature flag - will result in errors when validating the Pod. All - of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - startupProbe: - description: 'StartupProbe indicates that the Pod has successfully - initialized. If specified, no other probes are executed until - this completes successfully. If this probe fails, the Pod - will be restarted, just as if the livenessProbe failed. This - can be used to provide different probe parameters at the beginning - of a Pod''s lifecycle, when it might take a long time to load - data or warm a cache, than during steady-state operation. - This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - stdin: - description: Whether this container should allocate a buffer - for stdin in the container runtime. If this is not set, reads - from stdin in the container will always result in EOF. Default - is false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the - stdin channel after it has been opened by a single attach. - When stdin is true the stdin stream will remain open across - multiple attach sessions. If stdinOnce is set to true, stdin - is opened on container start, is empty until the first client - attaches to stdin, and then remains open and accepts data - until the client disconnects, at which time stdin is closed - and remains closed until the container is restarted. If this - flag is false, a container processes that reads from stdin - will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the - container''s termination message will be written is mounted - into the container''s filesystem. Message written is intended - to be brief final status, such as an assertion failure message. - Will be truncated by the node if greater than 4096 bytes. - The total message length across all containers will be limited - to 12kb. Defaults to /dev/termination-log. Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be - populated. File will use the contents of terminationMessagePath - to populate the container status message on both success and - failure. FallbackToLogsOnError will use the last chunk of - container log output if the termination message file is empty - and the container exited with an error. The log output is - limited to 2048 bytes or 80 lines, whichever is smaller. Defaults - to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - devicePath - - name - type: object - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume - within a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other - way around. When not set, MountPropagationNone is used. - This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - subPathExpr: - description: Expanded path within the volume from which - the container's volume should be mounted. Behaves similarly - to SubPath but environment variable references $(VAR_NAME) - are expanded using the container's environment. Defaults - to "" (volume's root). SubPathExpr and SubPath are mutually - exclusive. - type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might - be configured in the container image. Cannot be updated. - type: string - required: - - name - type: object - type: array - externalUrl: - description: The external URL the Alertmanager instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Alertmanager is not served from root of a DNS name. - type: string - forceEnableClusterMode: - description: ForceEnableClusterMode ensures Alertmanager does not - deactivate the cluster mode when running with a single replica. - Use case is e.g. spanning an Alertmanager cluster across Kubernetes - clusters with a single replica in each. - type: boolean - hostAliases: - description: Pods' hostAliases configuration - items: - description: HostAlias holds the mapping between IP and hostnames - that will be injected as an entry in the pod's hosts file. - properties: - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - ip: - description: IP address of the host file entry. - type: string - required: - - hostnames - - ip - type: object - type: array - x-kubernetes-list-map-keys: - - ip - x-kubernetes-list-type: map - image: - description: Image if specified has precedence over baseImage, tag - and sha combinations. Specifying the version is still necessary - to ensure the Prometheus Operator knows what version of Alertmanager - is being configured. - type: string - imagePullPolicy: - description: Image pull policy for the 'alertmanager', 'init-config-reloader' - and 'config-reloader' containers. See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy - for more details. - enum: - - "" - - Always - - Never - - IfNotPresent - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same - namespace to use for pulling prometheus and alertmanager images - from registries see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to - let you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - type: array - initContainers: - description: 'InitContainers allows adding initContainers to the pod - definition. Those can be used to e.g. fetch secrets for injection - into the Alertmanager configuration from external sources. Any errors - during the execution of an initContainer will lead to a restart - of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ - InitContainers described here modify an operator generated init - containers if they share the same name and modifications are done - via a strategic merge patch. The current init container name is: - `init-config-reloader`. Overriding init containers is entirely outside - the scope of what the maintainers will support and by doing so, - you accept that this behaviour may break at any time without notice.' - items: - description: A single application container that you want to run - within a pod. - properties: - args: - description: 'Arguments to the entrypoint. The container image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will - be unchanged. Double $$ are reduced to a single $, which allows - for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will - produce the string literal "$(VAR_NAME)". Escaped references - will never be expanded, regardless of whether the variable - exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. - The container image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: - i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether - the variable exists or not. Cannot be updated. More info: - https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in - the container and any service environment variables. - If a variable cannot be resolved, the reference in the - input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) - syntax: i.e. "$$(VAR_NAME)" will produce the string - literal "$(VAR_NAME)". Escaped references will never - be expanded, regardless of whether the variable exists - or not. Defaults to "".' - type: string - valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. - properties: - configMapKeyRef: - description: Selects a key of a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format of the - exposed resources, defaults to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - description: Selects a key of a secret in the pod's - namespace - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be - a C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key - will take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set - of ConfigMaps - properties: - configMapRef: - description: The ConfigMap to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap must be - defined - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: The Secret to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - image: - description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Actions that the management system should take - in response to container lifecycle events. Cannot be updated. - properties: - postStart: - description: 'PostStart is called immediately after a container - is created. If the handler fails, the container is terminated - and restarted according to its restart policy. Other management - of the container blocks until the hook completes. More - info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - description: 'PreStop is called immediately before a container - is terminated due to an API request or management event - such as liveness/startup probe failure, preemption, resource - contention, etc. The handler is not called if the container - crashes or exits. The Pod''s termination grace period - countdown begins before the PreStop hook is executed. - Regardless of the outcome of the handler, the container - will eventually terminate within the Pod''s termination - grace period (unless delayed by finalizers). Other management - of the container blocks until the hook completes or until - the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - description: 'Periodic probe of container liveness. Container - will be restarted if the probe fails. Cannot be updated. More - info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - name: - description: Name of the container specified as a DNS_LABEL. - Each container in a pod must have a unique name (DNS_LABEL). - Cannot be updated. - type: string - ports: - description: List of ports to expose from the container. Not - specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Modifying this array with strategic merge patch may corrupt - the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. - Cannot be updated. - items: - description: ContainerPort represents a network port in a - single container. - properties: - containerPort: - description: Number of port to expose on the pod's IP - address. This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If - specified, this must be a valid port number, 0 < x < - 65536. If HostNetwork is specified, this must match - ContainerPort. Most containers do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod - must have a unique name. Name for the port that can - be referred to by services. - type: string - protocol: - default: TCP - description: Protocol for port. Must be UDP, TCP, or SCTP. - Defaults to "TCP". - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - description: 'Periodic probe of container service readiness. - Container will be removed from service endpoints if the probe - fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - resizePolicy: - description: Resources resize policy for the container. - items: - description: ContainerResizePolicy represents resource resize - policy for the container. - properties: - resourceName: - description: 'Name of the resource to which this resource - resize policy applies. Supported values: cpu, memory.' - type: string - restartPolicy: - description: Restart policy to apply when specified resource - is resized. If not specified, it defaults to NotRequired. - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - description: 'Compute Resources required by this container. - Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - securityContext: - description: 'SecurityContext defines the security options the - container should be run with. If set, the fields of SecurityContext - override the equivalent fields of PodSecurityContext. More - info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by - the container runtime. Note that this field cannot be - set when spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent to - root on the host. Defaults to false. Note that this field - cannot be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to - use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. Note that this field cannot - be set when spec.os.name is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a - non-root user. If true, the Kubelet will validate the - image at runtime to ensure that it does not run as UID - 0 (root) and fail to start the container if it does. If - unset or false, no such validation will be performed. - May also be set in PodSecurityContext. If set in both - SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a - random SELinux context for each container. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. - Note that this field cannot be set when spec.os.name is - windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile - must be preconfigured on the node to work. Must be - a descending path, relative to the kubelet's configured - seccomp profile location. Must only be set if type - is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should be - used. RuntimeDefault - the container runtime default - profile should be used. Unconfined - no profile should - be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components - that enable the WindowsHostProcessContainers feature - flag. Setting this field without the feature flag - will result in errors when validating the Pod. All - of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - startupProbe: - description: 'StartupProbe indicates that the Pod has successfully - initialized. If specified, no other probes are executed until - this completes successfully. If this probe fails, the Pod - will be restarted, just as if the livenessProbe failed. This - can be used to provide different probe parameters at the beginning - of a Pod''s lifecycle, when it might take a long time to load - data or warm a cache, than during steady-state operation. - This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - stdin: - description: Whether this container should allocate a buffer - for stdin in the container runtime. If this is not set, reads - from stdin in the container will always result in EOF. Default - is false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the - stdin channel after it has been opened by a single attach. - When stdin is true the stdin stream will remain open across - multiple attach sessions. If stdinOnce is set to true, stdin - is opened on container start, is empty until the first client - attaches to stdin, and then remains open and accepts data - until the client disconnects, at which time stdin is closed - and remains closed until the container is restarted. If this - flag is false, a container processes that reads from stdin - will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the - container''s termination message will be written is mounted - into the container''s filesystem. Message written is intended - to be brief final status, such as an assertion failure message. - Will be truncated by the node if greater than 4096 bytes. - The total message length across all containers will be limited - to 12kb. Defaults to /dev/termination-log. Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be - populated. File will use the contents of terminationMessagePath - to populate the container status message on both success and - failure. FallbackToLogsOnError will use the last chunk of - container log output if the termination message file is empty - and the container exited with an error. The log output is - limited to 2048 bytes or 80 lines, whichever is smaller. Defaults - to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - devicePath - - name - type: object - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume - within a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other - way around. When not set, MountPropagationNone is used. - This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - subPathExpr: - description: Expanded path within the volume from which - the container's volume should be mounted. Behaves similarly - to SubPath but environment variable references $(VAR_NAME) - are expanded using the container's environment. Defaults - to "" (volume's root). SubPathExpr and SubPath are mutually - exclusive. - type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might - be configured in the container image. Cannot be updated. - type: string - required: - - name - type: object - type: array - listenLocal: - description: ListenLocal makes the Alertmanager server listen on loopback, - so that it does not bind against the Pod IP. Note this is only for - the Alertmanager UI, not the gossip communication. - type: boolean - logFormat: - description: Log format for Alertmanager to be configured with. - enum: - - "" - - logfmt - - json - type: string - logLevel: - description: Log level for Alertmanager to be configured with. - enum: - - "" - - debug - - info - - warn - - error - type: string - minReadySeconds: - description: Minimum number of seconds for which a newly created pod - should be ready without any of its container crashing for it to - be considered available. Defaults to 0 (pod will be considered available - as soon as it is ready) This is an alpha field from kubernetes 1.22 - until 1.24 which requires enabling the StatefulSetMinReadySeconds - feature gate. - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: Define which Nodes the Pods are scheduled on. - type: object - paused: - description: If set to true all actions on the underlying managed - objects are not goint to be performed, except for delete actions. - type: boolean - podMetadata: - description: PodMetadata configures Labels and Annotations which are - propagated to the alertmanager pods. - properties: - annotations: - additionalProperties: - type: string - description: 'Annotations is an unstructured key value map stored - with a resource that may be set by external tools to store and - retrieve arbitrary metadata. They are not queryable and should - be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - labels: - additionalProperties: - type: string - description: 'Map of string keys and values that can be used to - organize and categorize (scope and select) objects. May match - selectors of replication controllers and services. More info: - http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. Is required - when creating resources, although some resources may allow a - client to request the generation of an appropriate name automatically. - Name is primarily intended for creation idempotence and configuration - definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - type: object - portName: - default: web - description: Port name used for the pods and governing service. Defaults - to `web`. - type: string - priorityClassName: - description: Priority class assigned to the Pods - type: string - replicas: - description: Size is the expected size of the alertmanager cluster. - The controller will eventually make the size of the running cluster - equal to the expected size. - format: int32 - type: integer - resources: - description: Define resources requests and limits for single Pods. - properties: - claims: - description: "Claims lists the names of resources, defined in - spec.resourceClaims, that are used by this container. \n This - is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only be set - for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry in pod.spec.resourceClaims - of the Pod where this field is used. It makes that resource - available inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - retention: - default: 120h - description: Time duration Alertmanager shall retain data for. Default - is '120h', and must match the regular expression `[0-9]+(ms|s|m|h)` - (milliseconds seconds minutes hours). - pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - routePrefix: - description: The route prefix Alertmanager registers HTTP handlers - for. This is useful, if using ExternalURL and a proxy is rewriting - HTTP routes of a request, and the actual ExternalURL is still true, - but the server serves requests under a different route prefix. For - example for use with `kubectl proxy`. - type: string - secrets: - description: Secrets is a list of Secrets in the same namespace as - the Alertmanager object, which shall be mounted into the Alertmanager - Pods. Each Secret is added to the StatefulSet definition as a volume - named `secret-`. The Secrets are mounted into `/etc/alertmanager/secrets/` - in the 'alertmanager' container. - items: - type: string - type: array - securityContext: - description: SecurityContext holds pod-level security attributes and - common container settings. This defaults to the default PodSecurityContext. - properties: - fsGroup: - description: "A special supplemental group that applies to all - containers in a pod. Some volume types allow the Kubelet to - change the ownership of that volume to be owned by the pod: - \n 1. The owning GID will be the FSGroup 2. The setgid bit is - set (new files created in the volume will be owned by FSGroup) - 3. The permission bits are OR'd with rw-rw---- \n If unset, - the Kubelet will not modify the ownership and permissions of - any volume. Note that this field cannot be set when spec.os.name - is windows." - format: int64 - type: integer - fsGroupChangePolicy: - description: 'fsGroupChangePolicy defines behavior of changing - ownership and permission of the volume before being exposed - inside Pod. This field will only apply to volume types which - support fsGroup based ownership(and permissions). It will have - no effect on ephemeral volume types such as: secret, configmaps - and emptydir. Valid values are "OnRootMismatch" and "Always". - If not specified, "Always" is used. Note that this field cannot - be set when spec.os.name is windows.' - type: string - runAsGroup: - description: The GID to run the entrypoint of the container process. - Uses runtime default if unset. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - Note that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to start - the container if it does. If unset or false, no such validation - will be performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container process. - Defaults to user specified in image metadata if unspecified. - May also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this field cannot - be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all containers. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - Note that this field cannot be set when spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies to - the container. - type: string - role: - description: Role is a SELinux role label that applies to - the container. - type: string - type: - description: Type is a SELinux type label that applies to - the container. - type: string - user: - description: User is a SELinux user label that applies to - the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by the containers in this - pod. Note that this field cannot be set when spec.os.name is - windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must be - preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a profile - defined in a file on the node should be used. RuntimeDefault - - the container runtime default profile should be used. - Unconfined - no profile should be applied." - type: string - required: - - type - type: object - supplementalGroups: - description: A list of groups applied to the first process run - in each container, in addition to the container's primary GID, - the fsGroup (if specified), and group memberships defined in - the container image for the uid of the container process. If - unspecified, no additional groups are added to any container. - Note that group memberships defined in the container image for - the uid of the container process are still effective, even if - they are not included in this list. Note that this field cannot - be set when spec.os.name is windows. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used for - the pod. Pods with unsupported sysctls (by the container runtime) - might fail to launch. Note that this field cannot be set when - spec.os.name is windows. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to all containers. - If unspecified, the options within a container's SecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named by - the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the GMSA - credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is alpha-level - and will only be honored by components that enable the WindowsHostProcessContainers - feature flag. Setting this field without the feature flag - will result in errors when validating the Pod. All of a - Pod's containers must have the same effective HostProcess - value (it is not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in PodSecurityContext. - If set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: string - type: object - type: object - serviceAccountName: - description: ServiceAccountName is the name of the ServiceAccount - to use to run the Prometheus Pods. - type: string - sha: - description: 'SHA of Alertmanager container image to be deployed. - Defaults to the value of `version`. Similar to a tag, but the SHA - explicitly deploys an immutable container image. Version and Tag - are ignored if SHA is set. Deprecated: use ''image'' instead. The - image digest can be specified as part of the image URL.' - type: string - storage: - description: Storage is the definition of how storage will be used - by the Alertmanager instances. - properties: - disableMountSubPath: - description: 'Deprecated: subPath usage will be disabled by default - in a future release, this option will become unnecessary. DisableMountSubPath - allows to remove any subPath usage in volume mounts.' - type: boolean - emptyDir: - description: 'EmptyDirVolumeSource to be used by the StatefulSet. - If specified, used in place of any volumeClaimTemplate. More - info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir' - properties: - medium: - description: 'medium represents what type of storage medium - should back this directory. The default is "" which means - to use the node''s default medium. Must be an empty string - (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: - anyOf: - - type: integer - - type: string - description: 'sizeLimit is the total amount of local storage - required for this EmptyDir volume. The size limit is also - applicable for memory medium. The maximum usage on memory - medium EmptyDir would be the minimum value between the SizeLimit - specified here and the sum of memory limits of all containers - in a pod. The default is nil which means that the limit - is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - ephemeral: - description: 'EphemeralVolumeSource to be used by the StatefulSet. - This is a beta field in k8s 1.21, for lower versions, starting - with k8s 1.19, it requires enabling the GenericEphemeralVolume - feature gate. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes' - properties: - volumeClaimTemplate: - description: "Will be used to create a stand-alone PVC to - provision the volume. The pod in which this EphemeralVolumeSource - is embedded will be the owner of the PVC, i.e. the PVC will - be deleted together with the pod. The name of the PVC will - be `-` where `` is the - name from the `PodSpec.Volumes` array entry. Pod validation - will reject the pod if the concatenated name is not valid - for a PVC (for example, too long). \n An existing PVC with - that name that is not owned by the pod will *not* be used - for the pod to avoid using an unrelated volume by mistake. - Starting the pod is then blocked until the unrelated PVC - is removed. If such a pre-created PVC is meant to be used - by the pod, the PVC has to updated with an owner reference - to the pod once the pod exists. Normally this should not - be necessary, but it may be useful when manually reconstructing - a broken cluster. \n This field is read-only and no changes - will be made by Kubernetes to the PVC after it has been - created. \n Required, must not be nil." - properties: - metadata: - description: May contain labels and annotations that will - be copied into the PVC when creating it. No other fields - are allowed and will be rejected during validation. - type: object - spec: - description: The specification for the PersistentVolumeClaim. - The entire content is copied unchanged into the PVC - that gets created from this template. The same fields - as in a PersistentVolumeClaim are also valid here. - properties: - accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the - provisioner or an external controller can support - the specified data source, it will create a new - volume based on the contents of the specified data - source. When the AnyVolumeDataSource feature gate - is enabled, dataSource contents will be copied to - dataSourceRef, and dataSourceRef contents will be - copied to dataSource when dataSourceRef.namespace - is not specified. If the namespace is specified, - then dataSourceRef will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object from - which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a - non-empty API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, volume binding - will only succeed if the type of the specified object - matches some installed volume populator or dynamic - provisioner. This field will replace the functionality - of the dataSource field and as such if both fields - are non-empty, they must have the same value. For - backwards compatibility, when namespace isn''t specified - in dataSourceRef, both fields (dataSource and dataSourceRef) - will be set to the same value automatically if one - of them is empty and the other is non-empty. When - namespace is specified in dataSourceRef, dataSource - isn''t set to the same value and must be empty. - There are three important differences between dataSource - and dataSourceRef: * While dataSource only allows - two specific types of objects, dataSourceRef allows - any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all values, - and generates an error if a disallowed value is - specified. * While dataSource only allows local - objects, dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using the namespace - field of dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: Namespace is the namespace of resource - being referenced Note that when a namespace - is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept the - reference. See the ReferenceGrant documentation - for details. (Alpha) This field requires the - CrossNamespaceVolumeDataSource feature gate - to be enabled. - type: string - required: - - kind - - name - type: object - resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify - resource requirements that are lower than previous - value but must still be higher than capacity recorded - in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' - properties: - claims: - description: "Claims lists the names of resources, - defined in spec.resourceClaims, that are used - by this container. \n This is an alpha field - and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It - can only be set for containers." - items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of - one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes - that resource available inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount - of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount - of compute resources required. If Requests is - omitted for a container, it defaults to Limits - if that is explicitly specified, otherwise to - an implementation-defined value. Requests cannot - exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement is - a selector that contains values, a key, and - an operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If - the operator is Exists or DoesNotExist, - the values array must be empty. This array - is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - description: 'storageClassName is the name of the - StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume - is required by the claim. Value of Filesystem is - implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to - the PersistentVolume backing this claim. - type: string - type: object - required: - - spec - type: object - type: object - volumeClaimTemplate: - description: A PVC spec to be used by the StatefulSet. The easiest - way to use a volume that cannot be automatically provisioned - (for whatever reason) is to use a label selector alongside manually - created PersistentVolumes. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST - resource this object represents. Servers may infer this - from the endpoint the client submits requests to. Cannot - be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - description: EmbeddedMetadata contains metadata relevant to - an EmbeddedResource. - properties: - annotations: - additionalProperties: - type: string - description: 'Annotations is an unstructured key value - map stored with a resource that may be set by external - tools to store and retrieve arbitrary metadata. They - are not queryable and should be preserved when modifying - objects. More info: http://kubernetes.io/docs/user-guide/annotations' - type: object - labels: - additionalProperties: - type: string - description: 'Map of string keys and values that can be - used to organize and categorize (scope and select) objects. - May match selectors of replication controllers and services. - More info: http://kubernetes.io/docs/user-guide/labels' - type: object - name: - description: 'Name must be unique within a namespace. - Is required when creating resources, although some resources - may allow a client to request the generation of an appropriate - name automatically. Name is primarily intended for creation - idempotence and configuration definition. Cannot be - updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' - type: string - type: object - spec: - description: 'Spec defines the desired characteristics of - a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the provisioner - or an external controller can support the specified - data source, it will create a new volume based on the - contents of the specified data source. When the AnyVolumeDataSource - feature gate is enabled, dataSource contents will be - copied to dataSourceRef, and dataSourceRef contents - will be copied to dataSource when dataSourceRef.namespace - is not specified. If the namespace is specified, then - dataSourceRef will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being referenced - type: string - name: - description: Name is the name of resource being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object from - which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a non-empty - API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, volume binding - will only succeed if the type of the specified object - matches some installed volume populator or dynamic provisioner. - This field will replace the functionality of the dataSource - field and as such if both fields are non-empty, they - must have the same value. For backwards compatibility, - when namespace isn''t specified in dataSourceRef, both - fields (dataSource and dataSourceRef) will be set to - the same value automatically if one of them is empty - and the other is non-empty. When namespace is specified - in dataSourceRef, dataSource isn''t set to the same - value and must be empty. There are three important differences - between dataSource and dataSourceRef: * While dataSource - only allows two specific types of objects, dataSourceRef - allows any non-core object, as well as PersistentVolumeClaim - objects. * While dataSource ignores disallowed values - (dropping them), dataSourceRef preserves all values, - and generates an error if a disallowed value is specified. - * While dataSource only allows local objects, dataSourceRef - allows objects in any namespaces. (Beta) Using this - field requires the AnyVolumeDataSource feature gate - to be enabled. (Alpha) Using the namespace field of - dataSourceRef requires the CrossNamespaceVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being referenced - type: string - name: - description: Name is the name of resource being referenced - type: string - namespace: - description: Namespace is the namespace of resource - being referenced Note that when a namespace is specified, - a gateway.networking.k8s.io/ReferenceGrant object - is required in the referent namespace to allow that - namespace's owner to accept the reference. See the - ReferenceGrant documentation for details. (Alpha) - This field requires the CrossNamespaceVolumeDataSource - feature gate to be enabled. - type: string - required: - - kind - - name - type: object - resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify resource - requirements that are lower than previous value but - must still be higher than capacity recorded in the status - field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' - properties: - claims: - description: "Claims lists the names of resources, - defined in spec.resourceClaims, that are used by - this container. \n This is an alpha field and requires - enabling the DynamicResourceAllocation feature gate. - \n This field is immutable. It can only be set for - containers." - items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one - entry in pod.spec.resourceClaims of the Pod - where this field is used. It makes that resource - available inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount - of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. Requests cannot exceed Limits. More info: - https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - selector: - description: selector is a label query over volumes to - consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - description: 'storageClassName is the name of the StorageClass - required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is - required by the claim. Value of Filesystem is implied - when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - type: object - status: - description: 'Status represents the current information/status - of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - accessModes: - description: 'accessModes contains the actual access modes - the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: allocatedResources is the storage resource - within AllocatedResources tracks the capacity allocated - to a PVC. It may be larger than the actual capacity - when a volume expansion operation is requested. For - storage quota, the larger value from allocatedResources - and PVC.spec.resources is used. If allocatedResources - is not set, PVC.spec.resources alone is used for quota - calculation. If a volume expansion capacity request - is lowered, allocatedResources is only lowered if there - are no expansion operations in progress and if the actual - volume capacity is equal or lower than the requested - capacity. This is an alpha field and requires enabling - RecoverVolumeExpansionFailure feature. - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual resources - of the underlying volume. - type: object - conditions: - description: conditions is the current Condition of persistent - volume claim. If underlying persistent volume is being - resized then the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contains - details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time we probed - the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the time the - condition transitioned from one status to another. - format: date-time - type: string - message: - description: message is the human-readable message - indicating details about last transition. - type: string - reason: - description: reason is a unique, this should be - a short, machine understandable string that gives - the reason for condition's last transition. If - it reports "ResizeStarted" that means the underlying - persistent volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - phase: - description: phase represents the current phase of PersistentVolumeClaim. - type: string - resizeStatus: - description: resizeStatus stores status of resize operation. - ResizeStatus is not set by default but when expansion - is complete resizeStatus is set to empty string by resize - controller or kubelet. This is an alpha field and requires - enabling RecoverVolumeExpansionFailure feature. - type: string - type: object - type: object - type: object - tag: - description: 'Tag of Alertmanager container image to be deployed. - Defaults to the value of `version`. Version is ignored if Tag is - set. Deprecated: use ''image'' instead. The image tag can be specified - as part of the image URL.' - type: string - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates any - taint that matches the triple using the matching - operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match all - values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod - can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, it - is not set, which means tolerate the taint forever (do not - evict). Zero and negative values will be treated as 0 (evict - immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: If specified, the pod's topology spread constraints. - items: - description: TopologySpreadConstraint specifies how to spread matching - pods among the given topology. - properties: - labelSelector: - description: LabelSelector is used to find matching pods. Pods - that match this label selector are counted to determine the - number of pods in their corresponding topology domain. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists or - DoesNotExist, the values array must be empty. This - array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: "MatchLabelKeys is a set of pod label keys to select - the pods over which spreading will be calculated. The keys - are used to lookup values from the incoming pod labels, those - key-value labels are ANDed with labelSelector to select the - group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in - both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot - be set when LabelSelector isn't set. Keys that don't exist - in the incoming pod labels will be ignored. A null or empty - list means only match against labelSelector. \n This is a - beta field and requires the MatchLabelKeysInPodTopologySpread - feature gate to be enabled (enabled by default)." - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: 'MaxSkew describes the degree to which pods may - be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, - it is the maximum permitted difference between the number - of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods - in an eligible domain or zero if the number of eligible domains - is less than MinDomains. For example, in a 3-zone cluster, - MaxSkew is set to 1, and pods with the same labelSelector - spread as 2/2/1: In this case, the global minimum is 1. | - zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew - is 1, incoming pod can only be scheduled to zone3 to become - 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) - on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming - pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, - it is used to give higher precedence to topologies that satisfy - it. It''s a required field. Default value is 1 and 0 is not - allowed.' - format: int32 - type: integer - minDomains: - description: "MinDomains indicates a minimum number of eligible - domains. When the number of eligible domains with matching - topology keys is less than minDomains, Pod Topology Spread - treats \"global minimum\" as 0, and then the calculation of - Skew is performed. And when the number of eligible domains - with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. As a result, when - the number of eligible domains is less than minDomains, scheduler - won't schedule more than maxSkew Pods to those domains. If - value is nil, the constraint behaves as if MinDomains is equal - to 1. Valid values are integers greater than 0. When value - is not nil, WhenUnsatisfiable must be DoNotSchedule. \n For - example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains - is set to 5 and pods with the same labelSelector spread as - 2/2/2: | zone1 | zone2 | zone3 | | P P | P P | P P | - The number of domains is less than 5(MinDomains), so \"global - minimum\" is treated as 0. In this situation, new pod with - the same labelSelector cannot be scheduled, because computed - skew will be 3(3 - 0) if new Pod is scheduled to any of the - three zones, it will violate MaxSkew. \n This is a beta field - and requires the MinDomainsInPodTopologySpread feature gate - to be enabled (enabled by default)." - format: int32 - type: integer - nodeAffinityPolicy: - description: "NodeAffinityPolicy indicates how we will treat - Pod's nodeAffinity/nodeSelector when calculating pod topology - spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector - are included in the calculations. - Ignore: nodeAffinity/nodeSelector - are ignored. All nodes are included in the calculations. \n - If this value is nil, the behavior is equivalent to the Honor - policy. This is a beta-level feature default enabled by the - NodeInclusionPolicyInPodTopologySpread feature flag." - type: string - nodeTaintsPolicy: - description: "NodeTaintsPolicy indicates how we will treat node - taints when calculating pod topology spread skew. Options - are: - Honor: nodes without taints, along with tainted nodes - for which the incoming pod has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - \n If this value is nil, the behavior is equivalent to the - Ignore policy. This is a beta-level feature default enabled - by the NodeInclusionPolicyInPodTopologySpread feature flag." - type: string - topologyKey: - description: TopologyKey is the key of node labels. Nodes that - have a label with this key and identical values are considered - to be in the same topology. We consider each - as a "bucket", and try to put balanced number of pods into - each bucket. We define a domain as a particular instance of - a topology. Also, we define an eligible domain as a domain - whose nodes meet the requirements of nodeAffinityPolicy and - nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", - each Node is a domain of that topology. And, if TopologyKey - is "topology.kubernetes.io/zone", each zone is a domain of - that topology. It's a required field. - type: string - whenUnsatisfiable: - description: 'WhenUnsatisfiable indicates how to deal with a - pod if it doesn''t satisfy the spread constraint. - DoNotSchedule - (default) tells the scheduler not to schedule it. - ScheduleAnyway - tells the scheduler to schedule the pod in any location, but - giving higher precedence to topologies that would help reduce - the skew. A constraint is considered "Unsatisfiable" for an - incoming pod if and only if every possible node assignment - for that pod would violate "MaxSkew" on some topology. For - example, in a 3-zone cluster, MaxSkew is set to 1, and pods - with the same labelSelector spread as 3/1/1: | zone1 | zone2 - | zone3 | | P P P | P | P | If WhenUnsatisfiable is - set to DoNotSchedule, incoming pod can only be scheduled to - zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on - zone2(zone3) satisfies MaxSkew(1). In other words, the cluster - can still be imbalanced, but scheduler won''t make it *more* - imbalanced. It''s a required field.' - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - version: - description: Version the cluster should be on. - type: string - volumeMounts: - description: VolumeMounts allows configuration of additional VolumeMounts - on the output StatefulSet definition. VolumeMounts specified will - be appended to other VolumeMounts in the alertmanager container, - that are generated as a result of StorageSpec objects. - items: - description: VolumeMount describes a mounting of a Volume within - a container. - properties: - mountPath: - description: Path within the container at which the volume should - be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are propagated - from the host to container and the other way around. When - not set, MountPropagationNone is used. This field is beta - in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - subPathExpr: - description: Expanded path within the volume from which the - container's volume should be mounted. Behaves similarly to - SubPath but environment variable references $(VAR_NAME) are - expanded using the container's environment. Defaults to "" - (volume's root). SubPathExpr and SubPath are mutually exclusive. - type: string - required: - - mountPath - - name - type: object - type: array - volumes: - description: Volumes allows configuration of additional volumes on - the output StatefulSet definition. Volumes specified will be appended - to other volumes that are generated as a result of StorageSpec objects. - items: - description: Volume represents a named volume in a pod that may - be accessed by any container in the pod. - properties: - awsElasticBlockStore: - description: 'awsElasticBlockStore represents an AWS Disk resource - that is attached to a kubelet''s host machine and then exposed - to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - properties: - fsType: - description: 'fsType is the filesystem type of the volume - that you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. Examples: - "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - partition: - description: 'partition is the partition in the volume that - you want to mount. If omitted, the default is to mount - by volume name. Examples: For volume /dev/sda1, you specify - the partition as "1". Similarly, the volume partition - for /dev/sda is "0" (or you can leave the property empty).' - format: int32 - type: integer - readOnly: - description: 'readOnly value true will force the readOnly - setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - type: boolean - volumeID: - description: 'volumeID is unique ID of the persistent disk - resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - type: string - required: - - volumeID - type: object - azureDisk: - description: azureDisk represents an Azure Data Disk mount on - the host and bind mount to the pod. - properties: - cachingMode: - description: 'cachingMode is the Host Caching mode: None, - Read Only, Read Write.' - type: string - diskName: - description: diskName is the Name of the data disk in the - blob storage - type: string - diskURI: - description: diskURI is the URI of data disk in the blob - storage - type: string - fsType: - description: fsType is Filesystem type to mount. Must be - a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. - type: string - kind: - description: 'kind expected values are Shared: multiple - blob disks per storage account Dedicated: single blob - disk per storage account Managed: azure managed data - disk (only in managed availability set). defaults to shared' - type: string - readOnly: - description: readOnly Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - description: azureFile represents an Azure File Service mount - on the host and bind mount to the pod. - properties: - readOnly: - description: readOnly defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretName: - description: secretName is the name of secret that contains - Azure Storage Account Name and Key - type: string - shareName: - description: shareName is the azure share Name - type: string - required: - - secretName - - shareName - type: object - cephfs: - description: cephFS represents a Ceph FS mount on the host that - shares a pod's lifetime - properties: - monitors: - description: 'monitors is Required: Monitors is a collection - of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - items: - type: string - type: array - path: - description: 'path is Optional: Used as the mounted root, - rather than the full Ceph tree, default is /' - type: string - readOnly: - description: 'readOnly is Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts. - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: boolean - secretFile: - description: 'secretFile is Optional: SecretFile is the - path to key ring for User, default is /etc/ceph/user.secret - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: string - secretRef: - description: 'secretRef is Optional: SecretRef is reference - to the authentication secret for User, default is empty. - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - user: - description: 'user is optional: User is the rados user name, - default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: string - required: - - monitors - type: object - cinder: - description: 'cinder represents a cinder volume attached and - mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - properties: - fsType: - description: 'fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Examples: "ext4", "xfs", "ntfs". Implicitly inferred to - be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: string - readOnly: - description: 'readOnly defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: boolean - secretRef: - description: 'secretRef is optional: points to a secret - object containing parameters used to connect to OpenStack.' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: - description: 'volumeID used to identify the volume in cinder. - More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: string - required: - - volumeID - type: object - configMap: - description: configMap represents a configMap that should populate - this volume - properties: - defaultMode: - description: 'defaultMode is optional: mode bits used to - set permissions on created files by default. Must be an - octal value between 0000 and 0777 or a decimal value between - 0 and 511. YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. Defaults to - 0644. Directories within the path are not affected by - this setting. This might be in conflict with other options - that affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - items: - description: items if unspecified, each key-value pair in - the Data field of the referenced ConfigMap will be projected - into the volume as a file whose name is the key and content - is the value. If specified, the listed keys will be projected - into the specified paths, and unlisted keys will not be - present. If a key is specified which is not present in - the ConfigMap, the volume setup will error unless it is - marked optional. Paths must be relative and may not contain - the '..' path or start with '..'. - items: - description: Maps a string key to a path within a volume. - properties: - key: - description: key is the key to project. - type: string - mode: - description: 'mode is Optional: mode bits used to - set permissions on this file. Must be an octal value - between 0000 and 0777 or a decimal value between - 0 and 511. YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. If not - specified, the volume defaultMode will be used. - This might be in conflict with other options that - affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: path is the relative path of the file - to map the key to. May not be an absolute path. - May not contain the path element '..'. May not start - with the string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: optional specify whether the ConfigMap or its - keys must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - description: csi (Container Storage Interface) represents ephemeral - storage that is handled by certain external CSI drivers (Beta - feature). - properties: - driver: - description: driver is the name of the CSI driver that handles - this volume. Consult with your admin for the correct name - as registered in the cluster. - type: string - fsType: - description: fsType to mount. Ex. "ext4", "xfs", "ntfs". - If not provided, the empty value is passed to the associated - CSI driver which will determine the default filesystem - to apply. - type: string - nodePublishSecretRef: - description: nodePublishSecretRef is a reference to the - secret object containing sensitive information to pass - to the CSI driver to complete the CSI NodePublishVolume - and NodeUnpublishVolume calls. This field is optional, - and may be empty if no secret is required. If the secret - object contains more than one secret, all secret references - are passed. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - description: readOnly specifies a read-only configuration - for the volume. Defaults to false (read/write). - type: boolean - volumeAttributes: - additionalProperties: - type: string - description: volumeAttributes stores driver-specific properties - that are passed to the CSI driver. Consult your driver's - documentation for supported values. - type: object - required: - - driver - type: object - downwardAPI: - description: downwardAPI represents downward API about the pod - that should populate this volume - properties: - defaultMode: - description: 'Optional: mode bits to use on created files - by default. Must be a Optional: mode bits used to set - permissions on created files by default. Must be an octal - value between 0000 and 0777 or a decimal value between - 0 and 511. YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. Defaults to - 0644. Directories within the path are not affected by - this setting. This might be in conflict with other options - that affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - items: - description: Items is a list of downward API volume file - items: - description: DownwardAPIVolumeFile represents information - to create the file containing the pod field - properties: - fieldRef: - description: 'Required: Selects a field of the pod: - only annotations, labels, name and namespace are - supported.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - description: 'Optional: mode bits used to set permissions - on this file, must be an octal value between 0000 - and 0777 or a decimal value between 0 and 511. YAML - accepts both octal and decimal values, JSON requires - decimal values for mode bits. If not specified, - the volume defaultMode will be used. This might - be in conflict with other options that affect the - file mode, like fsGroup, and the result can be other - mode bits set.' - format: int32 - type: integer - path: - description: 'Required: Path is the relative path - name of the file to be created. Must not be absolute - or contain the ''..'' path. Must be utf-8 encoded. - The first item of the relative path must not start - with ''..''' - type: string - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, requests.cpu and requests.memory) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format of the - exposed resources, defaults to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - type: object - emptyDir: - description: 'emptyDir represents a temporary directory that - shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - properties: - medium: - description: 'medium represents what type of storage medium - should back this directory. The default is "" which means - to use the node''s default medium. Must be an empty string - (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: - anyOf: - - type: integer - - type: string - description: 'sizeLimit is the total amount of local storage - required for this EmptyDir volume. The size limit is also - applicable for memory medium. The maximum usage on memory - medium EmptyDir would be the minimum value between the - SizeLimit specified here and the sum of memory limits - of all containers in a pod. The default is nil which means - that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - ephemeral: - description: "ephemeral represents a volume that is handled - by a cluster storage driver. The volume's lifecycle is tied - to the pod that defines it - it will be created before the - pod starts, and deleted when the pod is removed. \n Use this - if: a) the volume is only needed while the pod runs, b) features - of normal volumes like restoring from snapshot or capacity - tracking are needed, c) the storage driver is specified through - a storage class, and d) the storage driver supports dynamic - volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource - for more information on the connection between this volume - type and PersistentVolumeClaim). \n Use PersistentVolumeClaim - or one of the vendor-specific APIs for volumes that persist - for longer than the lifecycle of an individual pod. \n Use - CSI for light-weight local ephemeral volumes if the CSI driver - is meant to be used that way - see the documentation of the - driver for more information. \n A pod can use both types of - ephemeral volumes and persistent volumes at the same time." - properties: - volumeClaimTemplate: - description: "Will be used to create a stand-alone PVC to - provision the volume. The pod in which this EphemeralVolumeSource - is embedded will be the owner of the PVC, i.e. the PVC - will be deleted together with the pod. The name of the - PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. - Pod validation will reject the pod if the concatenated - name is not valid for a PVC (for example, too long). \n - An existing PVC with that name that is not owned by the - pod will *not* be used for the pod to avoid using an unrelated - volume by mistake. Starting the pod is then blocked until - the unrelated PVC is removed. If such a pre-created PVC - is meant to be used by the pod, the PVC has to updated - with an owner reference to the pod once the pod exists. - Normally this should not be necessary, but it may be useful - when manually reconstructing a broken cluster. \n This - field is read-only and no changes will be made by Kubernetes - to the PVC after it has been created. \n Required, must - not be nil." - properties: - metadata: - description: May contain labels and annotations that - will be copied into the PVC when creating it. No other - fields are allowed and will be rejected during validation. - type: object - spec: - description: The specification for the PersistentVolumeClaim. - The entire content is copied unchanged into the PVC - that gets created from this template. The same fields - as in a PersistentVolumeClaim are also valid here. - properties: - accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the - provisioner or an external controller can support - the specified data source, it will create a new - volume based on the contents of the specified - data source. When the AnyVolumeDataSource feature - gate is enabled, dataSource contents will be copied - to dataSourceRef, and dataSourceRef contents will - be copied to dataSource when dataSourceRef.namespace - is not specified. If the namespace is specified, - then dataSourceRef will not be copied to dataSource.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API - group. For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: 'dataSourceRef specifies the object - from which to populate the volume with data, if - a non-empty volume is desired. This may be any - object from a non-empty API group (non core object) - or a PersistentVolumeClaim object. When this field - is specified, volume binding will only succeed - if the type of the specified object matches some - installed volume populator or dynamic provisioner. - This field will replace the functionality of the - dataSource field and as such if both fields are - non-empty, they must have the same value. For - backwards compatibility, when namespace isn''t - specified in dataSourceRef, both fields (dataSource - and dataSourceRef) will be set to the same value - automatically if one of them is empty and the - other is non-empty. When namespace is specified - in dataSourceRef, dataSource isn''t set to the - same value and must be empty. There are three - important differences between dataSource and dataSourceRef: - * While dataSource only allows two specific types - of objects, dataSourceRef allows any non-core - object, as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values (dropping - them), dataSourceRef preserves all values, and - generates an error if a disallowed value is specified. - * While dataSource only allows local objects, - dataSourceRef allows objects in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled. (Alpha) Using the - namespace field of dataSourceRef requires the - CrossNamespaceVolumeDataSource feature gate to - be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API - group. For any other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: Namespace is the namespace of resource - being referenced Note that when a namespace - is specified, a gateway.networking.k8s.io/ReferenceGrant - object is required in the referent namespace - to allow that namespace's owner to accept - the reference. See the ReferenceGrant documentation - for details. (Alpha) This field requires the - CrossNamespaceVolumeDataSource feature gate - to be enabled. - type: string - required: - - kind - - name - type: object - resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify - resource requirements that are lower than previous - value but must still be higher than capacity recorded - in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' - properties: - claims: - description: "Claims lists the names of resources, - defined in spec.resourceClaims, that are used - by this container. \n This is an alpha field - and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. - It can only be set for containers." - items: - description: ResourceClaim references one - entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name - of one entry in pod.spec.resourceClaims - of the Pod where this field is used. - It makes that resource available inside - a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount - of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum - amount of compute resources required. If Requests - is omitted for a container, it defaults to - Limits if that is explicitly specified, otherwise - to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - description: 'storageClassName is the name of the - StorageClass required by the claim. More info: - https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume - is required by the claim. Value of Filesystem - is implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference - to the PersistentVolume backing this claim. - type: string - type: object - required: - - spec - type: object - type: object - fc: - description: fc represents a Fibre Channel resource that is - attached to a kubelet's host machine and then exposed to the - pod. - properties: - fsType: - description: 'fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. TODO: how do we prevent errors in the - filesystem from compromising the machine' - type: string - lun: - description: 'lun is Optional: FC target lun number' - format: int32 - type: integer - readOnly: - description: 'readOnly is Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts.' - type: boolean - targetWWNs: - description: 'targetWWNs is Optional: FC target worldwide - names (WWNs)' - items: - type: string - type: array - wwids: - description: 'wwids Optional: FC volume world wide identifiers - (wwids) Either wwids or combination of targetWWNs and - lun must be set, but not both simultaneously.' - items: - type: string - type: array - type: object - flexVolume: - description: flexVolume represents a generic volume resource - that is provisioned/attached using an exec based plugin. - properties: - driver: - description: driver is the name of the driver to use for - this volume. - type: string - fsType: - description: fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". The default filesystem depends - on FlexVolume script. - type: string - options: - additionalProperties: - type: string - description: 'options is Optional: this field holds extra - command options if any.' - type: object - readOnly: - description: 'readOnly is Optional: defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts.' - type: boolean - secretRef: - description: 'secretRef is Optional: secretRef is reference - to the secret object containing sensitive information - to pass to the plugin scripts. This may be empty if no - secret object is specified. If the secret object contains - more than one secret, all secrets are passed to the plugin - scripts.' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - description: flocker represents a Flocker volume attached to - a kubelet's host machine. This depends on the Flocker control - service being running - properties: - datasetName: - description: datasetName is Name of the dataset stored as - metadata -> name on the dataset for Flocker should be - considered as deprecated - type: string - datasetUUID: - description: datasetUUID is the UUID of the dataset. This - is unique identifier of a Flocker dataset - type: string - type: object - gcePersistentDisk: - description: 'gcePersistentDisk represents a GCE Disk resource - that is attached to a kubelet''s host machine and then exposed - to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - properties: - fsType: - description: 'fsType is filesystem type of the volume that - you want to mount. Tip: Ensure that the filesystem type - is supported by the host operating system. Examples: "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - partition: - description: 'partition is the partition in the volume that - you want to mount. If omitted, the default is to mount - by volume name. Examples: For volume /dev/sda1, you specify - the partition as "1". Similarly, the volume partition - for /dev/sda is "0" (or you can leave the property empty). - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - format: int32 - type: integer - pdName: - description: 'pdName is unique name of the PD resource in - GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - type: string - readOnly: - description: 'readOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - type: boolean - required: - - pdName - type: object - gitRepo: - description: 'gitRepo represents a git repository at a particular - revision. DEPRECATED: GitRepo is deprecated. To provision - a container with a git repo, mount an EmptyDir into an InitContainer - that clones the repo using git, then mount the EmptyDir into - the Pod''s container.' - properties: - directory: - description: directory is the target directory name. Must - not contain or start with '..'. If '.' is supplied, the - volume directory will be the git repository. Otherwise, - if specified, the volume will contain the git repository - in the subdirectory with the given name. - type: string - repository: - description: repository is the URL - type: string - revision: - description: revision is the commit hash for the specified - revision. - type: string - required: - - repository - type: object - glusterfs: - description: 'glusterfs represents a Glusterfs mount on the - host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' - properties: - endpoints: - description: 'endpoints is the endpoint name that details - Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: string - path: - description: 'path is the Glusterfs volume path. More info: - https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: string - readOnly: - description: 'readOnly here will force the Glusterfs volume - to be mounted with read-only permissions. Defaults to - false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: boolean - required: - - endpoints - - path - type: object - hostPath: - description: 'hostPath represents a pre-existing file or directory - on the host machine that is directly exposed to the container. - This is generally used for system agents or other privileged - things that are allowed to see the host machine. Most containers - will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath - --- TODO(jonesdl) We need to restrict who can use host directory - mounts and who can/can not mount host directories as read/write.' - properties: - path: - description: 'path of the directory on the host. If the - path is a symlink, it will follow the link to the real - path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' - type: string - type: - description: 'type for HostPath Volume Defaults to "" More - info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' - type: string - required: - - path - type: object - iscsi: - description: 'iscsi represents an ISCSI Disk resource that is - attached to a kubelet''s host machine and then exposed to - the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' - properties: - chapAuthDiscovery: - description: chapAuthDiscovery defines whether support iSCSI - Discovery CHAP authentication - type: boolean - chapAuthSession: - description: chapAuthSession defines whether support iSCSI - Session CHAP authentication - type: boolean - fsType: - description: 'fsType is the filesystem type of the volume - that you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. Examples: - "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - initiatorName: - description: initiatorName is the custom iSCSI Initiator - Name. If initiatorName is specified with iscsiInterface - simultaneously, new iSCSI interface : will be created for the connection. - type: string - iqn: - description: iqn is the target iSCSI Qualified Name. - type: string - iscsiInterface: - description: iscsiInterface is the interface Name that uses - an iSCSI transport. Defaults to 'default' (tcp). - type: string - lun: - description: lun represents iSCSI Target Lun number. - format: int32 - type: integer - portals: - description: portals is the iSCSI Target Portal List. The - portal is either an IP or ip_addr:port if the port is - other than default (typically TCP ports 860 and 3260). - items: - type: string - type: array - readOnly: - description: readOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. - type: boolean - secretRef: - description: secretRef is the CHAP Secret for iSCSI target - and initiator authentication - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - description: targetPortal is iSCSI Target Portal. The Portal - is either an IP or ip_addr:port if the port is other than - default (typically TCP ports 860 and 3260). - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - description: 'name of the volume. Must be a DNS_LABEL and unique - within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - nfs: - description: 'nfs represents an NFS mount on the host that shares - a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - properties: - path: - description: 'path that is exported by the NFS server. More - info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: string - readOnly: - description: 'readOnly here will force the NFS export to - be mounted with read-only permissions. Defaults to false. - More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: boolean - server: - description: 'server is the hostname or IP address of the - NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - description: 'persistentVolumeClaimVolumeSource represents a - reference to a PersistentVolumeClaim in the same namespace. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - claimName: - description: 'claimName is the name of a PersistentVolumeClaim - in the same namespace as the pod using this volume. More - info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - type: string - readOnly: - description: readOnly Will force the ReadOnly setting in - VolumeMounts. Default false. - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - description: photonPersistentDisk represents a PhotonController - persistent disk attached and mounted on kubelets host machine - properties: - fsType: - description: fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. - type: string - pdID: - description: pdID is the ID that identifies Photon Controller - persistent disk - type: string - required: - - pdID - type: object - portworxVolume: - description: portworxVolume represents a portworx volume attached - and mounted on kubelets host machine - properties: - fsType: - description: fSType represents the filesystem type to mount - Must be a filesystem type supported by the host operating - system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" - if unspecified. - type: string - readOnly: - description: readOnly defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - volumeID: - description: volumeID uniquely identifies a Portworx volume - type: string - required: - - volumeID - type: object - projected: - description: projected items for all in one resources secrets, - configmaps, and downward API - properties: - defaultMode: - description: defaultMode are the mode bits used to set permissions - on created files by default. Must be an octal value between - 0000 and 0777 or a decimal value between 0 and 511. YAML - accepts both octal and decimal values, JSON requires decimal - values for mode bits. Directories within the path are - not affected by this setting. This might be in conflict - with other options that affect the file mode, like fsGroup, - and the result can be other mode bits set. - format: int32 - type: integer - sources: - description: sources is the list of volume projections - items: - description: Projection that may be projected along with - other supported volume types - properties: - configMap: - description: configMap information about the configMap - data to project - properties: - items: - description: items if unspecified, each key-value - pair in the Data field of the referenced ConfigMap - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the ConfigMap, the volume - setup will error unless it is marked optional. - Paths must be relative and may not contain the - '..' path or start with '..'. - items: - description: Maps a string key to a path within - a volume. - properties: - key: - description: key is the key to project. - type: string - mode: - description: 'mode is Optional: mode bits - used to set permissions on this file. - Must be an octal value between 0000 and - 0777 or a decimal value between 0 and - 511. YAML accepts both octal and decimal - values, JSON requires decimal values for - mode bits. If not specified, the volume - defaultMode will be used. This might be - in conflict with other options that affect - the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: path is the relative path of - the file to map the key to. May not be - an absolute path. May not contain the - path element '..'. May not start with - the string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: optional specify whether the ConfigMap - or its keys must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - downwardAPI: - description: downwardAPI information about the downwardAPI - data to project - properties: - items: - description: Items is a list of DownwardAPIVolume - file - items: - description: DownwardAPIVolumeFile represents - information to create the file containing - the pod field - properties: - fieldRef: - description: 'Required: Selects a field - of the pod: only annotations, labels, - name and namespace are supported.' - properties: - apiVersion: - description: Version of the schema the - FieldPath is written in terms of, - defaults to "v1". - type: string - fieldPath: - description: Path of the field to select - in the specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - description: 'Optional: mode bits used to - set permissions on this file, must be - an octal value between 0000 and 0777 or - a decimal value between 0 and 511. YAML - accepts both octal and decimal values, - JSON requires decimal values for mode - bits. If not specified, the volume defaultMode - will be used. This might be in conflict - with other options that affect the file - mode, like fsGroup, and the result can - be other mode bits set.' - format: int32 - type: integer - path: - description: 'Required: Path is the relative - path name of the file to be created. Must - not be absolute or contain the ''..'' - path. Must be utf-8 encoded. The first - item of the relative path must not start - with ''..''' - type: string - resourceFieldRef: - description: 'Selects a resource of the - container: only resources limits and requests - (limits.cpu, limits.memory, requests.cpu - and requests.memory) are currently supported.' - properties: - containerName: - description: 'Container name: required - for volumes, optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format - of the exposed resources, defaults - to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to - select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - type: object - secret: - description: secret information about the secret data - to project - properties: - items: - description: items if unspecified, each key-value - pair in the Data field of the referenced Secret - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified which - is not present in the Secret, the volume setup - will error unless it is marked optional. Paths - must be relative and may not contain the '..' - path or start with '..'. - items: - description: Maps a string key to a path within - a volume. - properties: - key: - description: key is the key to project. - type: string - mode: - description: 'mode is Optional: mode bits - used to set permissions on this file. - Must be an octal value between 0000 and - 0777 or a decimal value between 0 and - 511. YAML accepts both octal and decimal - values, JSON requires decimal values for - mode bits. If not specified, the volume - defaultMode will be used. This might be - in conflict with other options that affect - the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: path is the relative path of - the file to map the key to. May not be - an absolute path. May not contain the - path element '..'. May not start with - the string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: optional field specify whether the - Secret or its key must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - serviceAccountToken: - description: serviceAccountToken is information about - the serviceAccountToken data to project - properties: - audience: - description: audience is the intended audience - of the token. A recipient of a token must identify - itself with an identifier specified in the audience - of the token, and otherwise should reject the - token. The audience defaults to the identifier - of the apiserver. - type: string - expirationSeconds: - description: expirationSeconds is the requested - duration of validity of the service account - token. As the token approaches expiration, the - kubelet volume plugin will proactively rotate - the service account token. The kubelet will - start trying to rotate the token if the token - is older than 80 percent of its time to live - or if the token is older than 24 hours.Defaults - to 1 hour and must be at least 10 minutes. - format: int64 - type: integer - path: - description: path is the path relative to the - mount point of the file to project the token - into. - type: string - required: - - path - type: object - type: object - type: array - type: object - quobyte: - description: quobyte represents a Quobyte mount on the host - that shares a pod's lifetime - properties: - group: - description: group to map volume access to Default is no - group - type: string - readOnly: - description: readOnly here will force the Quobyte volume - to be mounted with read-only permissions. Defaults to - false. - type: boolean - registry: - description: registry represents a single or multiple Quobyte - Registry services specified as a string as host:port pair - (multiple entries are separated with commas) which acts - as the central registry for volumes - type: string - tenant: - description: tenant owning the given Quobyte volume in the - Backend Used with dynamically provisioned Quobyte volumes, - value is set by the plugin - type: string - user: - description: user to map volume access to Defaults to serivceaccount - user - type: string - volume: - description: volume is a string that references an already - created Quobyte volume by name. - type: string - required: - - registry - - volume - type: object - rbd: - description: 'rbd represents a Rados Block Device mount on the - host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' - properties: - fsType: - description: 'fsType is the filesystem type of the volume - that you want to mount. Tip: Ensure that the filesystem - type is supported by the host operating system. Examples: - "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - image: - description: 'image is the rados image name. More info: - https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - keyring: - description: 'keyring is the path to key ring for RBDUser. - Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - monitors: - description: 'monitors is a collection of Ceph monitors. - More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - items: - type: string - type: array - pool: - description: 'pool is the rados pool name. Default is rbd. - More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - readOnly: - description: 'readOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: boolean - secretRef: - description: 'secretRef is name of the authentication secret - for RBDUser. If provided overrides keyring. Default is - nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - user: - description: 'user is the rados user name. Default is admin. - More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - required: - - image - - monitors - type: object - scaleIO: - description: scaleIO represents a ScaleIO persistent volume - attached and mounted on Kubernetes nodes. - properties: - fsType: - description: fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Default is "xfs". - type: string - gateway: - description: gateway is the host address of the ScaleIO - API Gateway. - type: string - protectionDomain: - description: protectionDomain is the name of the ScaleIO - Protection Domain for the configured storage. - type: string - readOnly: - description: readOnly Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretRef: - description: secretRef references to the secret for ScaleIO - user and other sensitive information. If this is not provided, - Login operation will fail. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - sslEnabled: - description: sslEnabled Flag enable/disable SSL communication - with Gateway, default false - type: boolean - storageMode: - description: storageMode indicates whether the storage for - a volume should be ThickProvisioned or ThinProvisioned. - Default is ThinProvisioned. - type: string - storagePool: - description: storagePool is the ScaleIO Storage Pool associated - with the protection domain. - type: string - system: - description: system is the name of the storage system as - configured in ScaleIO. - type: string - volumeName: - description: volumeName is the name of a volume already - created in the ScaleIO system that is associated with - this volume source. - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - description: 'secret represents a secret that should populate - this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' - properties: - defaultMode: - description: 'defaultMode is Optional: mode bits used to - set permissions on created files by default. Must be an - octal value between 0000 and 0777 or a decimal value between - 0 and 511. YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. Defaults to - 0644. Directories within the path are not affected by - this setting. This might be in conflict with other options - that affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - items: - description: items If unspecified, each key-value pair in - the Data field of the referenced Secret will be projected - into the volume as a file whose name is the key and content - is the value. If specified, the listed keys will be projected - into the specified paths, and unlisted keys will not be - present. If a key is specified which is not present in - the Secret, the volume setup will error unless it is marked - optional. Paths must be relative and may not contain the - '..' path or start with '..'. - items: - description: Maps a string key to a path within a volume. - properties: - key: - description: key is the key to project. - type: string - mode: - description: 'mode is Optional: mode bits used to - set permissions on this file. Must be an octal value - between 0000 and 0777 or a decimal value between - 0 and 511. YAML accepts both octal and decimal values, - JSON requires decimal values for mode bits. If not - specified, the volume defaultMode will be used. - This might be in conflict with other options that - affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: path is the relative path of the file - to map the key to. May not be an absolute path. - May not contain the path element '..'. May not start - with the string '..'. - type: string - required: - - key - - path - type: object - type: array - optional: - description: optional field specify whether the Secret or - its keys must be defined - type: boolean - secretName: - description: 'secretName is the name of the secret in the - pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' - type: string - type: object - storageos: - description: storageOS represents a StorageOS volume attached - and mounted on Kubernetes nodes. - properties: - fsType: - description: fsType is the filesystem type to mount. Must - be a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. - type: string - readOnly: - description: readOnly defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretRef: - description: secretRef specifies the secret to use for obtaining - the StorageOS API credentials. If not specified, default - values will be attempted. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - volumeName: - description: volumeName is the human-readable name of the - StorageOS volume. Volume names are only unique within - a namespace. - type: string - volumeNamespace: - description: volumeNamespace specifies the scope of the - volume within StorageOS. If no namespace is specified - then the Pod's namespace will be used. This allows the - Kubernetes name scoping to be mirrored within StorageOS - for tighter integration. Set VolumeName to any name to - override the default behaviour. Set to "default" if you - are not using namespaces within StorageOS. Namespaces - that do not pre-exist within StorageOS will be created. - type: string - type: object - vsphereVolume: - description: vsphereVolume represents a vSphere volume attached - and mounted on kubelets host machine - properties: - fsType: - description: fsType is filesystem type to mount. Must be - a filesystem type supported by the host operating system. - Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. - type: string - storagePolicyID: - description: storagePolicyID is the storage Policy Based - Management (SPBM) profile ID associated with the StoragePolicyName. - type: string - storagePolicyName: - description: storagePolicyName is the storage Policy Based - Management (SPBM) profile name. - type: string - volumePath: - description: volumePath is the path that identifies vSphere - volume vmdk - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - web: - description: Defines the web command line flags when starting Alertmanager. - properties: - getConcurrency: - description: Maximum number of GET requests processed concurrently. - This corresponds to the Alertmanager's `--web.get-concurrency` - flag. - format: int32 - type: integer - httpConfig: - description: Defines HTTP parameters for web server. - properties: - headers: - description: List of headers that can be added to HTTP responses. - properties: - contentSecurityPolicy: - description: Set the Content-Security-Policy header to - HTTP responses. Unset if blank. - type: string - strictTransportSecurity: - description: Set the Strict-Transport-Security header - to HTTP responses. Unset if blank. Please make sure - that you use this with care as this header might force - browsers to load Prometheus and the other applications - hosted on the same domain and subdomains over HTTPS. - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security - type: string - xContentTypeOptions: - description: Set the X-Content-Type-Options header to - HTTP responses. Unset if blank. Accepted value is nosniff. - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options - enum: - - "" - - NoSniff - type: string - xFrameOptions: - description: Set the X-Frame-Options header to HTTP responses. - Unset if blank. Accepted values are deny and sameorigin. - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options - enum: - - "" - - Deny - - SameOrigin - type: string - xXSSProtection: - description: Set the X-XSS-Protection header to all responses. - Unset if blank. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection - type: string - type: object - http2: - description: Enable HTTP/2 support. Note that HTTP/2 is only - supported with TLS. When TLSConfig is not configured, HTTP/2 - will be disabled. Whenever the value of the field changes, - a rolling update will be triggered. - type: boolean - type: object - timeout: - description: Timeout for HTTP requests. This corresponds to the - Alertmanager's `--web.timeout` flag. - format: int32 - type: integer - tlsConfig: - description: Defines the TLS parameters for HTTPS. - properties: - cert: - description: Contains the TLS certificate for the server. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cipherSuites: - description: 'List of supported cipher suites for TLS versions - up to TLS 1.2. If empty, Go default cipher suites are used. - Available cipher suites are documented in the go documentation: - https://golang.org/pkg/crypto/tls/#pkg-constants' - items: - type: string - type: array - client_ca: - description: Contains the CA certificate for client certificate - authentication to the server. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientAuthType: - description: 'Server policy for client authentication. Maps - to ClientAuth Policies. For more detail on clientAuth options: - https://golang.org/pkg/crypto/tls/#ClientAuthType' - type: string - curvePreferences: - description: 'Elliptic curves that will be used in an ECDHE - handshake, in preference order. Available curves are documented - in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID' - items: - type: string - type: array - keySecret: - description: Secret containing the TLS key for the server. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - description: Maximum TLS version that is acceptable. Defaults - to TLS13. - type: string - minVersion: - description: Minimum TLS version that is acceptable. Defaults - to TLS12. - type: string - preferServerCipherSuites: - description: Controls whether the server selects the client's - most preferred cipher suite, or the server's most preferred - cipher suite. If true then the server's preference, as expressed - in the order of elements in cipherSuites, is used. - type: boolean - required: - - cert - - keySecret - type: object - type: object - type: object - status: - description: 'Most recent observed status of the Alertmanager cluster. - Read-only. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' - properties: - availableReplicas: - description: Total number of available pods (ready for at least minReadySeconds) - targeted by this Alertmanager cluster. - format: int32 - type: integer - conditions: - description: The current state of the Alertmanager object. - items: - description: Condition represents the state of the resources associated - with the Prometheus, Alertmanager or ThanosRuler resource. - properties: - lastTransitionTime: - description: lastTransitionTime is the time of the last update - to the current status property. - format: date-time - type: string - message: - description: Human-readable message indicating details for the - condition's last transition. - type: string - observedGeneration: - description: ObservedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if `.metadata.generation` - is currently 12, but the `.status.conditions[].observedGeneration` - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - type: integer - reason: - description: Reason for the condition's last transition. - type: string - status: - description: Status of the condition. - type: string - type: - description: Type of the condition being reported. - type: string - required: - - lastTransitionTime - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - paused: - description: Represents whether any actions on the underlying managed - objects are being performed. Only delete actions will be performed. - type: boolean - replicas: - description: Total number of non-terminated pods targeted by this - Alertmanager object (their labels match the selector). - format: int32 - type: integer - unavailableReplicas: - description: Total number of unavailable pods targeted by this Alertmanager - object. - format: int32 - type: integer - updatedReplicas: - description: Total number of non-terminated pods targeted by this - Alertmanager object that have the desired version spec. - format: int32 - type: integer - required: - - availableReplicas - - paused - - replicas - - unavailableReplicas - - updatedReplicas - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_podmonitors.yaml b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_podmonitors.yaml deleted file mode 100644 index 76bf32903d..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_podmonitors.yaml +++ /dev/null @@ -1,684 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - name: podmonitors.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - categories: - - prometheus-operator - kind: PodMonitor - listKind: PodMonitorList - plural: podmonitors - shortNames: - - pmon - singular: podmonitor - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - description: PodMonitor defines monitoring for a set of pods. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Specification of desired Pod selection for target discovery - by Prometheus. - properties: - attachMetadata: - description: Attaches node metadata to discovered targets. Requires - Prometheus v2.35.0 and above. - properties: - node: - description: When set to true, Prometheus must have permissions - to get Nodes. - type: boolean - type: object - jobLabel: - description: The label to use to retrieve the job name from. - type: string - labelLimit: - description: Per-scrape limit on number of labels that will be accepted - for a sample. Only valid in Prometheus versions 2.27.0 and newer. - format: int64 - type: integer - labelNameLengthLimit: - description: Per-scrape limit on length of labels name that will be - accepted for a sample. Only valid in Prometheus versions 2.27.0 - and newer. - format: int64 - type: integer - labelValueLengthLimit: - description: Per-scrape limit on length of labels value that will - be accepted for a sample. Only valid in Prometheus versions 2.27.0 - and newer. - format: int64 - type: integer - namespaceSelector: - description: Selector to select which namespaces the Endpoints objects - are discovered from. - properties: - any: - description: Boolean describing whether all namespaces are selected - in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names to select from. - items: - type: string - type: array - type: object - podMetricsEndpoints: - description: A list of endpoints allowed as part of this PodMonitor. - items: - description: PodMetricsEndpoint defines a scrapeable endpoint of - a Kubernetes Pod serving Prometheus metrics. - properties: - authorization: - description: Authorization section for this endpoint - properties: - credentials: - description: The secret's key that contains the credentials - of the request - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults to Bearer, - Basic will cause an error - type: string - type: object - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over - basic authentication. More info: https://prometheus.io/docs/operating/configuration/#endpoint' - properties: - password: - description: The secret in the service monitor namespace - that contains the password for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor namespace - that contains the username for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: Secret to mount to read bearer token for scraping - targets. The secret needs to be in the same namespace as the - pod monitor and accessible by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - enableHttp2: - description: Whether to enable HTTP2. - type: boolean - filterRunning: - description: 'Drop pods that are not running. (Failed, Succeeded). - Enabled by default. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase' - type: boolean - followRedirects: - description: FollowRedirects configures whether scrape requests - follow HTTP 3xx redirects. - type: boolean - honorLabels: - description: HonorLabels chooses the metric's labels on collisions - with target labels. - type: boolean - honorTimestamps: - description: HonorTimestamps controls whether Prometheus respects - the timestamps present in scraped data. - type: boolean - interval: - description: Interval at which metrics should be scraped If - not specified Prometheus' global scrape interval is used. - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before - ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It - defines ``-section of Prometheus - configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - default: replace - description: Action to perform based on regex matching. - Default is 'replace'. uppercase and lowercase actions - require Prometheus >= 2.36. - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - description: Modulus to take of the hash of the source - label values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. Default is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular - expression for the replace, keep, and drop actions. - items: - description: LabelName is a valid Prometheus label name - which may only contain ASCII letters, numbers, as - well as underscores. - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: object - type: array - oauth2: - description: OAuth2 for the URL. Only valid in Prometheus versions - 2.27.0 and newer. - properties: - clientId: - description: The secret or configmap containing the OAuth2 - client id - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 client secret - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token URL - type: object - scopes: - description: OAuth2 scopes used for the token request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - params: - additionalProperties: - items: - type: string - type: array - description: Optional HTTP URL parameters - type: object - path: - description: HTTP path to scrape for metrics. If empty, Prometheus - uses the default value (e.g. `/metrics`). - type: string - port: - description: Name of the pod port this endpoint refers to. Mutually - exclusive with targetPort. - type: string - proxyUrl: - description: ProxyURL eg http://proxyserver:2195 Directs scrapes - to proxy through this endpoint. - type: string - relabelings: - description: 'RelabelConfigs to apply to samples before scraping. - Prometheus Operator automatically adds relabelings for a few - standard Kubernetes fields. The original scrape job''s name - is available via the `__tmp_prometheus_job_name` label. More - info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config' - items: - description: 'RelabelConfig allows dynamic rewriting of the - label set, being applied to samples before ingestion. It - defines ``-section of Prometheus - configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - default: replace - description: Action to perform based on regex matching. - Default is 'replace'. uppercase and lowercase actions - require Prometheus >= 2.36. - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - description: Modulus to take of the hash of the source - label values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. Default is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex - capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular - expression for the replace, keep, and drop actions. - items: - description: LabelName is a valid Prometheus label name - which may only contain ASCII letters, numbers, as - well as underscores. - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: object - type: array - scheme: - description: HTTP scheme to use for scraping. `http` and `https` - are the expected values unless you rewrite the `__scheme__` - label via relabeling. If empty, Prometheus uses the default - value `http`. - enum: - - http - - https - type: string - scrapeTimeout: - description: Timeout after which the scrape is ended If not - specified, the Prometheus global scrape interval is used. - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetPort: - anyOf: - - type: integer - - type: string - description: 'Deprecated: Use ''port'' instead.' - x-kubernetes-int-or-string: true - tlsConfig: - description: TLS configuration to use when scraping the endpoint. - properties: - ca: - description: Certificate authority used when verifying server - certificates. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key file for the - targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the targets. - type: string - type: object - type: object - type: array - podTargetLabels: - description: PodTargetLabels transfers labels on the Kubernetes Pod - onto the target. - items: - type: string - type: array - sampleLimit: - description: SampleLimit defines per-scrape limit on number of scraped - samples that will be accepted. - format: int64 - type: integer - selector: - description: Selector to select Pod objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - targetLimit: - description: TargetLimit defines a limit on the number of scraped - targets that will be accepted. - format: int64 - type: integer - required: - - podMetricsEndpoints - - selector - type: object - required: - - spec - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_probes.yaml b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_probes.yaml deleted file mode 100644 index 6f1163707a..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_probes.yaml +++ /dev/null @@ -1,727 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - name: probes.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - categories: - - prometheus-operator - kind: Probe - listKind: ProbeList - plural: probes - shortNames: - - prb - singular: probe - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - description: Probe defines monitoring for a set of static targets or ingresses. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Specification of desired Ingress selection for target discovery - by Prometheus. - properties: - authorization: - description: Authorization section for this endpoint - properties: - credentials: - description: The secret's key that contains the credentials of - the request - properties: - key: - description: The key of the secret to select from. Must be - a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be - defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - description: Set the authentication type. Defaults to Bearer, - Basic will cause an error - type: string - type: object - basicAuth: - description: 'BasicAuth allow an endpoint to authenticate over basic - authentication. More info: https://prometheus.io/docs/operating/configuration/#endpoint' - properties: - password: - description: The secret in the service monitor namespace that - contains the password for authentication. - properties: - key: - description: The key of the secret to select from. Must be - a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be - defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor namespace that - contains the username for authentication. - properties: - key: - description: The key of the secret to select from. Must be - a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be - defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenSecret: - description: Secret to mount to read bearer token for scraping targets. - The secret needs to be in the same namespace as the probe and accessible - by the Prometheus Operator. - properties: - key: - description: The key of the secret to select from. Must be a - valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - interval: - description: Interval at which targets are probed using the configured - prober. If not specified Prometheus' global scrape interval is used. - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - jobName: - description: The job name assigned to scraped metrics by default. - type: string - labelLimit: - description: Per-scrape limit on number of labels that will be accepted - for a sample. Only valid in Prometheus versions 2.27.0 and newer. - format: int64 - type: integer - labelNameLengthLimit: - description: Per-scrape limit on length of labels name that will be - accepted for a sample. Only valid in Prometheus versions 2.27.0 - and newer. - format: int64 - type: integer - labelValueLengthLimit: - description: Per-scrape limit on length of labels value that will - be accepted for a sample. Only valid in Prometheus versions 2.27.0 - and newer. - format: int64 - type: integer - metricRelabelings: - description: MetricRelabelConfigs to apply to samples before ingestion. - items: - description: 'RelabelConfig allows dynamic rewriting of the label - set, being applied to samples before ingestion. It defines ``-section - of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - default: replace - description: Action to perform based on regex matching. Default - is 'replace'. uppercase and lowercase actions require Prometheus - >= 2.36. - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - description: Modulus to take of the hash of the source label - values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. Default is '(.*)' - type: string - replacement: - description: Replacement value against which a regex replace - is performed if the regular expression matches. Regex capture - groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source label - values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing labels. - Their content is concatenated using the configured separator - and matched against the configured regular expression for - the replace, keep, and drop actions. - items: - description: LabelName is a valid Prometheus label name which - may only contain ASCII letters, numbers, as well as underscores. - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - description: Label to which the resulting value is written in - a replace action. It is mandatory for replace actions. Regex - capture groups are available. - type: string - type: object - type: array - module: - description: 'The module to use for probing specifying how to probe - the target. Example module configuring in the blackbox exporter: - https://github.com/prometheus/blackbox_exporter/blob/master/example.yml' - type: string - oauth2: - description: OAuth2 for the URL. Only valid in Prometheus versions - 2.27.0 and newer. - properties: - clientId: - description: The secret or configmap containing the OAuth2 client - id - properties: - configMap: - description: ConfigMap containing data to use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - description: The secret containing the OAuth2 client secret - properties: - key: - description: The key of the secret to select from. Must be - a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be - defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - description: Parameters to append to the token URL - type: object - scopes: - description: OAuth2 scopes used for the token request - items: - type: string - type: array - tokenUrl: - description: The URL to fetch the token from - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - prober: - description: Specification for the prober to use for probing targets. - The prober.URL parameter is required. Targets cannot be probed if - left empty. - properties: - path: - default: /probe - description: Path to collect metrics from. Defaults to `/probe`. - type: string - proxyUrl: - description: Optional ProxyURL. - type: string - scheme: - description: HTTP scheme to use for scraping. `http` and `https` - are the expected values unless you rewrite the `__scheme__` - label via relabeling. If empty, Prometheus uses the default - value `http`. - enum: - - http - - https - type: string - url: - description: Mandatory URL of the prober. - type: string - required: - - url - type: object - sampleLimit: - description: SampleLimit defines per-scrape limit on number of scraped - samples that will be accepted. - format: int64 - type: integer - scrapeTimeout: - description: Timeout for scraping metrics from the Prometheus exporter. - If not specified, the Prometheus global scrape timeout is used. - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetLimit: - description: TargetLimit defines a limit on the number of scraped - targets that will be accepted. - format: int64 - type: integer - targets: - description: Targets defines a set of static or dynamically discovered - targets to probe. - properties: - ingress: - description: ingress defines the Ingress objects to probe and - the relabeling configuration. If `staticConfig` is also defined, - `staticConfig` takes precedence. - properties: - namespaceSelector: - description: From which namespaces to select Ingress objects. - properties: - any: - description: Boolean describing whether all namespaces - are selected in contrast to a list restricting them. - type: boolean - matchNames: - description: List of namespace names to select from. - items: - type: string - type: array - type: object - relabelingConfigs: - description: 'RelabelConfigs to apply to the label set of - the target before it gets scraped. The original ingress - address is available via the `__tmp_prometheus_ingress_address` - label. It can be used to customize the probed URL. The original - scrape job''s name is available via the `__tmp_prometheus_job_name` - label. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config' - items: - description: 'RelabelConfig allows dynamic rewriting of - the label set, being applied to samples before ingestion. - It defines ``-section of Prometheus - configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - default: replace - description: Action to perform based on regex matching. - Default is 'replace'. uppercase and lowercase actions - require Prometheus >= 2.36. - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - description: Modulus to take of the hash of the source - label values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. Default is '(.*)' - type: string - replacement: - description: Replacement value against which a regex - replace is performed if the regular expression matches. - Regex capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular - expression for the replace, keep, and drop actions. - items: - description: LabelName is a valid Prometheus label - name which may only contain ASCII letters, numbers, - as well as underscores. - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: object - type: array - selector: - description: Selector to select the Ingress objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists or - DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - staticConfig: - description: 'staticConfig defines the static list of targets - to probe and the relabeling configuration. If `ingress` is also - defined, `staticConfig` takes precedence. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config.' - properties: - labels: - additionalProperties: - type: string - description: Labels assigned to all metrics scraped from the - targets. - type: object - relabelingConfigs: - description: 'RelabelConfigs to apply to the label set of - the targets before it gets scraped. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config' - items: - description: 'RelabelConfig allows dynamic rewriting of - the label set, being applied to samples before ingestion. - It defines ``-section of Prometheus - configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs' - properties: - action: - default: replace - description: Action to perform based on regex matching. - Default is 'replace'. uppercase and lowercase actions - require Prometheus >= 2.36. - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - description: Modulus to take of the hash of the source - label values. - format: int64 - type: integer - regex: - description: Regular expression against which the extracted - value is matched. Default is '(.*)' - type: string - replacement: - description: Replacement value against which a regex - replace is performed if the regular expression matches. - Regex capture groups are available. Default is '$1' - type: string - separator: - description: Separator placed between concatenated source - label values. default is ';'. - type: string - sourceLabels: - description: The source labels select values from existing - labels. Their content is concatenated using the configured - separator and matched against the configured regular - expression for the replace, keep, and drop actions. - items: - description: LabelName is a valid Prometheus label - name which may only contain ASCII letters, numbers, - as well as underscores. - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - description: Label to which the resulting value is written - in a replace action. It is mandatory for replace actions. - Regex capture groups are available. - type: string - type: object - type: array - static: - description: The list of hosts to probe. - items: - type: string - type: array - type: object - type: object - tlsConfig: - description: TLS configuration to use when scraping the endpoint. - properties: - ca: - description: Certificate authority used when verifying server - certificates. - properties: - configMap: - description: ConfigMap containing data to use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - description: Client certificate to present when doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to use for the targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keySecret: - description: Secret containing the client key file for the targets. - properties: - key: - description: The key of the secret to select from. Must be - a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be - defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the targets. - type: string - type: object - type: object - required: - - spec - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_prometheusagents.yaml b/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_prometheusagents.yaml deleted file mode 100644 index 848b223b5d..0000000000 --- a/testdata/bundles/registry-v1/prometheus-operator.v1.0.0/manifests/monitoring.coreos.com_prometheusagents.yaml +++ /dev/null @@ -1,8022 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - name: prometheusagents.monitoring.coreos.com -spec: - group: monitoring.coreos.com - names: - categories: - - prometheus-operator - kind: PrometheusAgent - listKind: PrometheusAgentList - plural: prometheusagents - shortNames: - - promagent - singular: prometheusagent - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The version of Prometheus agent - jsonPath: .spec.version - name: Version - type: string - - description: The number of desired replicas - jsonPath: .spec.replicas - name: Desired - type: integer - - description: The number of ready replicas - jsonPath: .status.availableReplicas - name: Ready - type: integer - - jsonPath: .status.conditions[?(@.type == 'Reconciled')].status - name: Reconciled - type: string - - jsonPath: .status.conditions[?(@.type == 'Available')].status - name: Available - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - description: Whether the resource reconciliation is paused or not - jsonPath: .status.paused - name: Paused - priority: 1 - type: boolean - name: v1alpha1 - schema: - openAPIV3Schema: - description: PrometheusAgent defines a Prometheus agent deployment. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: 'Specification of the desired behavior of the Prometheus - agent. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' - properties: - additionalArgs: - description: AdditionalArgs allows setting additional arguments for - the Prometheus container. It is intended for e.g. activating hidden - flags which are not supported by the dedicated configuration options - yet. The arguments are passed as-is to the Prometheus container - which may cause issues if they are invalid or not supported by the - given Prometheus version. In case of an argument conflict (e.g. - an argument which is already set by the operator itself) or when - providing an invalid argument the reconciliation will fail and an - error will be logged. - items: - description: Argument as part of the AdditionalArgs list. - properties: - name: - description: Name of the argument, e.g. "scrape.discovery-reload-interval". - minLength: 1 - type: string - value: - description: Argument value, e.g. 30s. Can be empty for name-only - arguments (e.g. --storage.tsdb.no-lockfile) - type: string - required: - - name - type: object - type: array - additionalScrapeConfigs: - description: 'AdditionalScrapeConfigs allows specifying a key of a - Secret containing additional Prometheus scrape configurations. Scrape - configurations specified are appended to the configurations generated - by the Prometheus Operator. Job configurations specified must have - the form as specified in the official Prometheus documentation: - https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config. - As scrape configs are appended, the user is responsible to make - sure it is valid. Note that using this feature may expose the possibility - to break upgrades of Prometheus. It is advised to review Prometheus - release notes to ensure that no incompatible scrape configs are - going to break Prometheus after the upgrade.' - properties: - key: - description: The key of the secret to select from. Must be a - valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - affinity: - description: If specified, the pod's scheduling constraints. - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the - pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the affinity expressions specified by - this field, but it may choose a node that violates one or - more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node matches - the corresponding matchExpressions; the node(s) with the - highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches - all objects with implicit weight 0 (i.e. it's a no-op). - A null preferred scheduling term matches no objects (i.e. - is also a no-op). - properties: - preference: - description: A node selector term, associated with the - corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to an update), the system may or may not try to - eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: A null or empty node selector term matches - no objects. The requirements of them are ANDed. The - TopologySelectorTerm type implements a subset of the - NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists, DoesNotExist. Gt, and - Lt. - type: string - values: - description: An array of string values. If - the operator is In or NotIn, the values - array must be non-empty. If the operator - is Exists or DoesNotExist, the values array - must be empty. If the operator is Gt or - Lt, the values array must have a single - element, which will be interpreted as an - integer. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - x-kubernetes-map-type: atomic - type: array - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate - this pod in the same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the affinity expressions specified by - this field, but it may choose a node that violates one or - more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node has - pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this - field are not met at scheduling time, the pod will not be - scheduled onto the node. If the affinity requirements specified - by this field cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may or may - not try to eventually evict the pod from its node. When - there are multiple elements, the lists of nodes corresponding - to each podAffinityTerm are intersected, i.e. all terms - must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of - pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied to the - union of the namespaces selected by this field and - the ones listed in the namespaces field. null selector - and null or empty namespaces list means "this pod's - namespace". An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace - names that the term applies to. The term is applied - to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. null or - empty namespaces list and null namespaceSelector means - "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of - any node on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. - avoid putting this pod in the same node, zone, etc. as some - other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to - nodes that satisfy the anti-affinity expressions specified - by this field, but it may choose a node that violates one - or more of the expressions. The node that is most preferred - is the one with the greatest sum of weights, i.e. for each - node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, - etc.), compute a sum by iterating through the elements of - this field and adding "weight" to the sum if the node has - pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the corresponding - podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the anti-affinity requirements - specified by this field cease to be met at some point during - pod execution (e.g. due to a pod label update), the system - may or may not try to eventually evict the pod from its - node. When there are multiple elements, the lists of nodes - corresponding to each podAffinityTerm are intersected, i.e. - all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not co-located - (anti-affinity) with, where co-located is defined as running - on a node whose value of the label with key - matches that of any node on which a pod of the set of - pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied to the - union of the namespaces selected by this field and - the ones listed in the namespaces field. null selector - and null or empty namespaces list means "this pod's - namespace". An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a - selector that contains values, a key, and an - operator that relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. If the - operator is Exists or DoesNotExist, the - values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is "In", - and the values array contains only "value". The - requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace - names that the term applies to. The term is applied - to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. null or - empty namespaces list and null namespaceSelector means - "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where - co-located is defined as running on a node whose value - of the label with key topologyKey matches that of - any node on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - apiserverConfig: - description: APIServerConfig allows specifying a host and auth methods - to access apiserver. If left empty, Prometheus is assumed to run - inside of the cluster and will discover API servers automatically - and use the pod's CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/. - properties: - authorization: - description: Authorization section for accessing apiserver - properties: - credentials: - description: The secret's key that contains the credentials - of the request - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - credentialsFile: - description: File to read a secret from, mutually exclusive - with Credentials (from SafeAuthorization) - type: string - type: - description: Set the authentication type. Defaults to Bearer, - Basic will cause an error - type: string - type: object - basicAuth: - description: BasicAuth allow an endpoint to authenticate over - basic authentication - properties: - password: - description: The secret in the service monitor namespace that - contains the password for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - description: The secret in the service monitor namespace that - contains the username for authentication. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerToken: - description: Bearer token for accessing apiserver. - type: string - bearerTokenFile: - description: File to read bearer token for accessing apiserver. - type: string - host: - description: Host of apiserver. A valid string consisting of a - hostname or IP followed by an optional port number - type: string - tlsConfig: - description: TLS Config to use for accessing apiserver. - properties: - ca: - description: Certificate authority used when verifying server - certificates. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - caFile: - description: Path to the CA cert in the Prometheus container - to use for the targets. - type: string - cert: - description: Client certificate to present when doing client-authentication. - properties: - configMap: - description: ConfigMap containing data to use for the - targets. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - description: Secret containing data to use for the targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its key - must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - certFile: - description: Path to the client cert file in the Prometheus - container for the targets. - type: string - insecureSkipVerify: - description: Disable target certificate validation. - type: boolean - keyFile: - description: Path to the client key file in the Prometheus - container for the targets. - type: string - keySecret: - description: Secret containing the client key file for the - targets. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the Secret or its key must - be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - serverName: - description: Used to verify the hostname for the targets. - type: string - type: object - required: - - host - type: object - arbitraryFSAccessThroughSMs: - description: ArbitraryFSAccessThroughSMs configures whether configuration - based on a service monitor can access arbitrary files on the file - system of the Prometheus container e.g. bearer token files. - properties: - deny: - type: boolean - type: object - configMaps: - description: ConfigMaps is a list of ConfigMaps in the same namespace - as the Prometheus object, which shall be mounted into the Prometheus - Pods. Each ConfigMap is added to the StatefulSet definition as a - volume named `configmap-`. The ConfigMaps are mounted - into /etc/prometheus/configmaps/ in the 'prometheus' - container. - items: - type: string - type: array - containers: - description: 'Containers allows injecting additional containers or - modifying operator generated containers. This can be used to allow - adding an authentication proxy to a Prometheus pod or to change - the behavior of an operator generated container. Containers described - here modify an operator generated container if they share the same - name and modifications are done via a strategic merge patch. The - current container names are: `prometheus`, `config-reloader`, and - `thanos-sidecar`. Overriding containers is entirely outside the - scope of what the maintainers will support and by doing so, you - accept that this behaviour may break at any time without notice.' - items: - description: A single application container that you want to run - within a pod. - properties: - args: - description: 'Arguments to the entrypoint. The container image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will - be unchanged. Double $$ are reduced to a single $, which allows - for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will - produce the string literal "$(VAR_NAME)". Escaped references - will never be expanded, regardless of whether the variable - exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. - The container image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: - i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether - the variable exists or not. Cannot be updated. More info: - https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in - the container and any service environment variables. - If a variable cannot be resolved, the reference in the - input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) - syntax: i.e. "$$(VAR_NAME)" will produce the string - literal "$(VAR_NAME)". Escaped references will never - be expanded, regardless of whether the variable exists - or not. Defaults to "".' - type: string - valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. - properties: - configMapKeyRef: - description: Selects a key of a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format of the - exposed resources, defaults to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - description: Selects a key of a secret in the pod's - namespace - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be - a C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key - will take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set - of ConfigMaps - properties: - configMapRef: - description: The ConfigMap to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap must be - defined - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: The Secret to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - image: - description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Actions that the management system should take - in response to container lifecycle events. Cannot be updated. - properties: - postStart: - description: 'PostStart is called immediately after a container - is created. If the handler fails, the container is terminated - and restarted according to its restart policy. Other management - of the container blocks until the hook completes. More - info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - description: 'PreStop is called immediately before a container - is terminated due to an API request or management event - such as liveness/startup probe failure, preemption, resource - contention, etc. The handler is not called if the container - crashes or exits. The Pod''s termination grace period - countdown begins before the PreStop hook is executed. - Regardless of the outcome of the handler, the container - will eventually terminate within the Pod''s termination - grace period (unless delayed by finalizers). Other management - of the container blocks until the hook completes or until - the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - description: 'Periodic probe of container liveness. Container - will be restarted if the probe fails. Cannot be updated. More - info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - name: - description: Name of the container specified as a DNS_LABEL. - Each container in a pod must have a unique name (DNS_LABEL). - Cannot be updated. - type: string - ports: - description: List of ports to expose from the container. Not - specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Modifying this array with strategic merge patch may corrupt - the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. - Cannot be updated. - items: - description: ContainerPort represents a network port in a - single container. - properties: - containerPort: - description: Number of port to expose on the pod's IP - address. This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If - specified, this must be a valid port number, 0 < x < - 65536. If HostNetwork is specified, this must match - ContainerPort. Most containers do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod - must have a unique name. Name for the port that can - be referred to by services. - type: string - protocol: - default: TCP - description: Protocol for port. Must be UDP, TCP, or SCTP. - Defaults to "TCP". - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - description: 'Periodic probe of container service readiness. - Container will be removed from service endpoints if the probe - fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - resizePolicy: - description: Resources resize policy for the container. - items: - description: ContainerResizePolicy represents resource resize - policy for the container. - properties: - resourceName: - description: 'Name of the resource to which this resource - resize policy applies. Supported values: cpu, memory.' - type: string - restartPolicy: - description: Restart policy to apply when specified resource - is resized. If not specified, it defaults to NotRequired. - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - description: 'Compute Resources required by this container. - Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - securityContext: - description: 'SecurityContext defines the security options the - container should be run with. If set, the fields of SecurityContext - override the equivalent fields of PodSecurityContext. More - info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by - the container runtime. Note that this field cannot be - set when spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent to - root on the host. Defaults to false. Note that this field - cannot be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to - use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. Note that this field cannot - be set when spec.os.name is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a - non-root user. If true, the Kubelet will validate the - image at runtime to ensure that it does not run as UID - 0 (root) and fail to start the container if it does. If - unset or false, no such validation will be performed. - May also be set in PodSecurityContext. If set in both - SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a - random SELinux context for each container. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. - Note that this field cannot be set when spec.os.name is - windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile - must be preconfigured on the node to work. Must be - a descending path, relative to the kubelet's configured - seccomp profile location. Must only be set if type - is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should be - used. RuntimeDefault - the container runtime default - profile should be used. Unconfined - no profile should - be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components - that enable the WindowsHostProcessContainers feature - flag. Setting this field without the feature flag - will result in errors when validating the Pod. All - of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - startupProbe: - description: 'StartupProbe indicates that the Pod has successfully - initialized. If specified, no other probes are executed until - this completes successfully. If this probe fails, the Pod - will be restarted, just as if the livenessProbe failed. This - can be used to provide different probe parameters at the beginning - of a Pod''s lifecycle, when it might take a long time to load - data or warm a cache, than during steady-state operation. - This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - stdin: - description: Whether this container should allocate a buffer - for stdin in the container runtime. If this is not set, reads - from stdin in the container will always result in EOF. Default - is false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the - stdin channel after it has been opened by a single attach. - When stdin is true the stdin stream will remain open across - multiple attach sessions. If stdinOnce is set to true, stdin - is opened on container start, is empty until the first client - attaches to stdin, and then remains open and accepts data - until the client disconnects, at which time stdin is closed - and remains closed until the container is restarted. If this - flag is false, a container processes that reads from stdin - will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the - container''s termination message will be written is mounted - into the container''s filesystem. Message written is intended - to be brief final status, such as an assertion failure message. - Will be truncated by the node if greater than 4096 bytes. - The total message length across all containers will be limited - to 12kb. Defaults to /dev/termination-log. Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be - populated. File will use the contents of terminationMessagePath - to populate the container status message on both success and - failure. FallbackToLogsOnError will use the last chunk of - container log output if the termination message file is empty - and the container exited with an error. The log output is - limited to 2048 bytes or 80 lines, whichever is smaller. Defaults - to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - devicePath - - name - type: object - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume - within a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other - way around. When not set, MountPropagationNone is used. - This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - subPathExpr: - description: Expanded path within the volume from which - the container's volume should be mounted. Behaves similarly - to SubPath but environment variable references $(VAR_NAME) - are expanded using the container's environment. Defaults - to "" (volume's root). SubPathExpr and SubPath are mutually - exclusive. - type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might - be configured in the container image. Cannot be updated. - type: string - required: - - name - type: object - type: array - enableFeatures: - description: Enable access to Prometheus disabled features. By default, - no features are enabled. Enabling disabled features is entirely - outside the scope of what the maintainers will support and by doing - so, you accept that this behaviour may break at any time without - notice. For more information see https://prometheus.io/docs/prometheus/latest/disabled_features/ - items: - type: string - type: array - enableRemoteWriteReceiver: - description: 'Enable Prometheus to be used as a receiver for the Prometheus - remote write protocol. Defaults to the value of `false`. WARNING: - This is not considered an efficient way of ingesting samples. Use - it with caution for specific low-volume use cases. It is not suitable - for replacing the ingestion via scraping and turning Prometheus - into a push-based metrics collection system. For more information - see https://prometheus.io/docs/prometheus/latest/querying/api/#remote-write-receiver - Only valid in Prometheus versions 2.33.0 and newer.' - type: boolean - enforcedBodySizeLimit: - description: 'EnforcedBodySizeLimit defines the maximum size of uncompressed - response body that will be accepted by Prometheus. Targets responding - with a body larger than this many bytes will cause the scrape to - fail. Example: 100MB. If defined, the limit will apply to all service/pod - monitors and probes. This is an experimental feature, this behaviour - could change or be removed in the future. Only valid in Prometheus - versions 2.28.0 and newer.' - pattern: (^0|([0-9]*[.])?[0-9]+((K|M|G|T|E|P)i?)?B)$ - type: string - enforcedLabelLimit: - description: Per-scrape limit on number of labels that will be accepted - for a sample. If more than this number of labels are present post - metric-relabeling, the entire scrape will be treated as failed. - 0 means no limit. Only valid in Prometheus versions 2.27.0 and newer. - format: int64 - type: integer - enforcedLabelNameLengthLimit: - description: Per-scrape limit on length of labels name that will be - accepted for a sample. If a label name is longer than this number - post metric-relabeling, the entire scrape will be treated as failed. - 0 means no limit. Only valid in Prometheus versions 2.27.0 and newer. - format: int64 - type: integer - enforcedLabelValueLengthLimit: - description: Per-scrape limit on length of labels value that will - be accepted for a sample. If a label value is longer than this number - post metric-relabeling, the entire scrape will be treated as failed. - 0 means no limit. Only valid in Prometheus versions 2.27.0 and newer. - format: int64 - type: integer - enforcedNamespaceLabel: - description: "EnforcedNamespaceLabel If set, a label will be added - to \n 1. all user-metrics (created by `ServiceMonitor`, `PodMonitor` - and `Probe` objects) and 2. in all `PrometheusRule` objects (except - the ones excluded in `prometheusRulesExcludedFromEnforce`) to * - alerting & recording rules and * the metrics used in their expressions - (`expr`). \n Label name is this field's value. Label value is the - namespace of the created object (mentioned above)." - type: string - enforcedSampleLimit: - description: EnforcedSampleLimit defines global limit on number of - scraped samples that will be accepted. This overrides any SampleLimit - set per ServiceMonitor or/and PodMonitor. It is meant to be used - by admins to enforce the SampleLimit to keep overall number of samples/series - under the desired limit. Note that if SampleLimit is lower that - value will be taken instead. - format: int64 - type: integer - enforcedTargetLimit: - description: EnforcedTargetLimit defines a global limit on the number - of scraped targets. This overrides any TargetLimit set per ServiceMonitor - or/and PodMonitor. It is meant to be used by admins to enforce - the TargetLimit to keep the overall number of targets under the - desired limit. Note that if TargetLimit is lower, that value will - be taken instead, except if either value is zero, in which case - the non-zero value will be used. If both values are zero, no limit - is enforced. - format: int64 - type: integer - excludedFromEnforcement: - description: List of references to PodMonitor, ServiceMonitor, Probe - and PrometheusRule objects to be excluded from enforcing a namespace - label of origin. Applies only if enforcedNamespaceLabel set to true. - items: - description: ObjectReference references a PodMonitor, ServiceMonitor, - Probe or PrometheusRule object. - properties: - group: - default: monitoring.coreos.com - description: Group of the referent. When not specified, it defaults - to `monitoring.coreos.com` - enum: - - monitoring.coreos.com - type: string - name: - description: Name of the referent. When not set, all resources - are matched. - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - minLength: 1 - type: string - resource: - description: Resource of the referent. - enum: - - prometheusrules - - servicemonitors - - podmonitors - - probes - type: string - required: - - namespace - - resource - type: object - type: array - externalLabels: - additionalProperties: - type: string - description: The labels to add to any time series or alerts when communicating - with external systems (federation, remote storage, Alertmanager). - type: object - externalUrl: - description: The external URL the Prometheus instances will be available - under. This is necessary to generate correct URLs. This is necessary - if Prometheus is not served from root of a DNS name. - type: string - hostAliases: - description: Pods' hostAliases configuration - items: - description: HostAlias holds the mapping between IP and hostnames - that will be injected as an entry in the pod's hosts file. - properties: - hostnames: - description: Hostnames for the above IP address. - items: - type: string - type: array - ip: - description: IP address of the host file entry. - type: string - required: - - hostnames - - ip - type: object - type: array - x-kubernetes-list-map-keys: - - ip - x-kubernetes-list-type: map - hostNetwork: - description: Use the host's network namespace if true. Make sure to - understand the security implications if you want to enable it. When - hostNetwork is enabled, this will set dnsPolicy to ClusterFirstWithHostNet - automatically. - type: boolean - ignoreNamespaceSelectors: - description: IgnoreNamespaceSelectors if set to true will ignore NamespaceSelector - settings from all PodMonitor, ServiceMonitor and Probe objects. - They will only discover endpoints within the namespace of the PodMonitor, - ServiceMonitor and Probe objects. Defaults to false. - type: boolean - image: - description: Image if specified has precedence over baseImage, tag - and sha combinations. Specifying the version is still necessary - to ensure the Prometheus Operator knows what version of Prometheus - is being configured. - type: string - imagePullPolicy: - description: Image pull policy for the 'prometheus', 'init-config-reloader' - and 'config-reloader' containers. See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy - for more details. - enum: - - "" - - Always - - Never - - IfNotPresent - type: string - imagePullSecrets: - description: An optional list of references to secrets in the same - namespace to use for pulling prometheus and alertmanager images - from registries see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod - items: - description: LocalObjectReference contains enough information to - let you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - x-kubernetes-map-type: atomic - type: array - initContainers: - description: 'InitContainers allows adding initContainers to the pod - definition. Those can be used to e.g. fetch secrets for injection - into the Prometheus configuration from external sources. Any errors - during the execution of an initContainer will lead to a restart - of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ - InitContainers described here modify an operator generated init - containers if they share the same name and modifications are done - via a strategic merge patch. The current init container name is: - `init-config-reloader`. Overriding init containers is entirely outside - the scope of what the maintainers will support and by doing so, - you accept that this behaviour may break at any time without notice.' - items: - description: A single application container that you want to run - within a pod. - properties: - args: - description: 'Arguments to the entrypoint. The container image''s - CMD is used if this is not provided. Variable references $(VAR_NAME) - are expanded using the container''s environment. If a variable - cannot be resolved, the reference in the input string will - be unchanged. Double $$ are reduced to a single $, which allows - for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will - produce the string literal "$(VAR_NAME)". Escaped references - will never be expanded, regardless of whether the variable - exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. - The container image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: - i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether - the variable exists or not. Cannot be updated. More info: - https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - Cannot be updated. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in - the container and any service environment variables. - If a variable cannot be resolved, the reference in the - input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) - syntax: i.e. "$$(VAR_NAME)" will produce the string - literal "$(VAR_NAME)". Escaped references will never - be expanded, regardless of whether the variable exists - or not. Defaults to "".' - type: string - valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. - properties: - configMapKeyRef: - description: Selects a key of a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, `metadata.labels['''']`, - `metadata.annotations['''']`, spec.nodeName, - spec.serviceAccountName, status.hostIP, status.podIP, - status.podIPs.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in the - specified API version. - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - anyOf: - - type: integer - - type: string - description: Specifies the output format of the - exposed resources, defaults to "1" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - description: Selects a key of a secret in the pod's - namespace - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must be - a C_IDENTIFIER. All invalid keys will be reported as an event - when the container is starting. When a key exists in multiple - sources, the value associated with the last source will take - precedence. Values defined by an Env with a duplicate key - will take precedence. Cannot be updated. - items: - description: EnvFromSource represents the source of a set - of ConfigMaps - properties: - configMapRef: - description: The ConfigMap to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap must be - defined - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: The Secret to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret must be defined - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - image: - description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images - This field is optional to allow higher level config management - to default or override container images in workload controllers - like Deployments and StatefulSets.' - type: string - imagePullPolicy: - description: 'Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always if :latest tag is specified, or IfNotPresent - otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' - type: string - lifecycle: - description: Actions that the management system should take - in response to container lifecycle events. Cannot be updated. - properties: - postStart: - description: 'PostStart is called immediately after a container - is created. If the handler fails, the container is terminated - and restarted according to its restart policy. Other management - of the container blocks until the hook completes. More - info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - description: 'PreStop is called immediately before a container - is terminated due to an API request or management event - such as liveness/startup probe failure, preemption, resource - contention, etc. The handler is not called if the container - crashes or exits. The Pod''s termination grace period - countdown begins before the PreStop hook is executed. - Regardless of the outcome of the handler, the container - will eventually terminate within the Pod''s termination - grace period (unless delayed by finalizers). Other management - of the container blocks until the hook completes or until - the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: Deprecated. TCPSocket is NOT supported - as a LifecycleHandler and kept for the backward compatibility. - There are no validation of this field and lifecycle - hooks will fail in runtime when tcp handler is specified. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - description: 'Periodic probe of container liveness. Container - will be restarted if the probe fails. Cannot be updated. More - info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - name: - description: Name of the container specified as a DNS_LABEL. - Each container in a pod must have a unique name (DNS_LABEL). - Cannot be updated. - type: string - ports: - description: List of ports to expose from the container. Not - specifying a port here DOES NOT prevent that port from being - exposed. Any port which is listening on the default "0.0.0.0" - address inside a container will be accessible from the network. - Modifying this array with strategic merge patch may corrupt - the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. - Cannot be updated. - items: - description: ContainerPort represents a network port in a - single container. - properties: - containerPort: - description: Number of port to expose on the pod's IP - address. This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port to. - type: string - hostPort: - description: Number of port to expose on the host. If - specified, this must be a valid port number, 0 < x < - 65536. If HostNetwork is specified, this must match - ContainerPort. Most containers do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod - must have a unique name. Name for the port that can - be referred to by services. - type: string - protocol: - default: TCP - description: Protocol for port. Must be UDP, TCP, or SCTP. - Defaults to "TCP". - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - description: 'Periodic probe of container service readiness. - Container will be removed from service endpoints if the probe - fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - resizePolicy: - description: Resources resize policy for the container. - items: - description: ContainerResizePolicy represents resource resize - policy for the container. - properties: - resourceName: - description: 'Name of the resource to which this resource - resize policy applies. Supported values: cpu, memory.' - type: string - restartPolicy: - description: Restart policy to apply when specified resource - is resized. If not specified, it defaults to NotRequired. - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - description: 'Compute Resources required by this container. - Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - securityContext: - description: 'SecurityContext defines the security options the - container should be run with. If set, the fields of SecurityContext - override the equivalent fields of PodSecurityContext. More - info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by - the container runtime. Note that this field cannot be - set when spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent to - root on the host. Defaults to false. Note that this field - cannot be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to - use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. Note that this field cannot - be set when spec.os.name is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a - non-root user. If true, the Kubelet will validate the - image at runtime to ensure that it does not run as UID - 0 (root) and fail to start the container if it does. If - unset or false, no such validation will be performed. - May also be set in PodSecurityContext. If set in both - SecurityContext and PodSecurityContext, the value specified - in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a - random SELinux context for each container. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. - Note that this field cannot be set when spec.os.name is - windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile - must be preconfigured on the node to work. Must be - a descending path, relative to the kubelet's configured - seccomp profile location. Must only be set if type - is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should be - used. RuntimeDefault - the container runtime default - profile should be used. Unconfined - no profile should - be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components - that enable the WindowsHostProcessContainers feature - flag. Setting this field without the feature flag - will result in errors when validating the Pod. All - of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - startupProbe: - description: 'StartupProbe indicates that the Pod has successfully - initialized. If specified, no other probes are executed until - this completes successfully. If this probe fails, the Pod - will be restarted, just as if the livenessProbe failed. This - can be used to provide different probe parameters at the beginning - of a Pod''s lifecycle, when it might take a long time to load - data or warm a cache, than during steady-state operation. - This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for the - command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', etc) - won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to - place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the - pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP - allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP - port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on - the container. Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and the - time when the processes are forcibly halted with a kill - signal. Set this value longer than the expected cleanup - time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, - this value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates - stop immediately via the kill signal (no opportunity to - shut down). This is a beta field and requires enabling - ProbeTerminationGracePeriod feature gate. Minimum value - is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times - out. Defaults to 1 second. Minimum value is 1. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - stdin: - description: Whether this container should allocate a buffer - for stdin in the container runtime. If this is not set, reads - from stdin in the container will always result in EOF. Default - is false. - type: boolean - stdinOnce: - description: Whether the container runtime should close the - stdin channel after it has been opened by a single attach. - When stdin is true the stdin stream will remain open across - multiple attach sessions. If stdinOnce is set to true, stdin - is opened on container start, is empty until the first client - attaches to stdin, and then remains open and accepts data - until the client disconnects, at which time stdin is closed - and remains closed until the container is restarted. If this - flag is false, a container processes that reads from stdin - will never receive an EOF. Default is false - type: boolean - terminationMessagePath: - description: 'Optional: Path at which the file to which the - container''s termination message will be written is mounted - into the container''s filesystem. Message written is intended - to be brief final status, such as an assertion failure message. - Will be truncated by the node if greater than 4096 bytes. - The total message length across all containers will be limited - to 12kb. Defaults to /dev/termination-log. Cannot be updated.' - type: string - terminationMessagePolicy: - description: Indicate how the termination message should be - populated. File will use the contents of terminationMessagePath - to populate the container status message on both success and - failure. FallbackToLogsOnError will use the last chunk of - container log output if the termination message file is empty - and the container exited with an error. The log output is - limited to 2048 bytes or 80 lines, whichever is smaller. Defaults - to File. Cannot be updated. - type: string - tty: - description: Whether this container should allocate a TTY for - itself, also requires 'stdin' to be true. Default is false. - type: boolean - volumeDevices: - description: volumeDevices is the list of block devices to be - used by the container. - items: - description: volumeDevice describes a mapping of a raw block - device within a container. - properties: - devicePath: - description: devicePath is the path inside of the container - that the device will be mapped to. - type: string - name: - description: name must match the name of a persistentVolumeClaim - in the pod - type: string - required: - - devicePath - - name - type: object - type: array - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - Cannot be updated. - items: - description: VolumeMount describes a mounting of a Volume - within a container. - properties: - mountPath: - description: Path within the container at which the volume - should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts are - propagated from the host to container and the other - way around. When not set, MountPropagationNone is used. - This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's root). - type: string - subPathExpr: - description: Expanded path within the volume from which - the container's volume should be mounted. Behaves similarly - to SubPath but environment variable references $(VAR_NAME) - are expanded using the container's environment. Defaults - to "" (volume's root). SubPathExpr and SubPath are mutually - exclusive. - type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might - be configured in the container image. Cannot be updated. - type: string - required: - - name - type: object - type: array - listenLocal: - description: ListenLocal makes the Prometheus server listen on loopback, - so that it does not bind against the Pod IP. - type: boolean - logFormat: - description: Log format for Prometheus to be configured with. - enum: - - "" - - logfmt - - json - type: string - logLevel: - description: Log level for Prometheus to be configured with. - enum: - - "" - - debug - - info - - warn - - error - type: string - minReadySeconds: - description: Minimum number of seconds for which a newly created pod - should be ready without any of its container crashing for it to - be considered available. Defaults to 0 (pod will be considered available - as soon as it is ready) This is an alpha field from kubernetes 1.22 - until 1.24 which requires enabling the StatefulSetMinReadySeconds - feature gate. - format: int32 - type: integer - nodeSelector: - additionalProperties: - type: string - description: Define which Nodes the Pods are scheduled on. - type: object - overrideHonorLabels: - description: When true, Prometheus resolves label conflicts by renaming - the labels in the scraped data to "exported_