diff --git a/Makefile b/Makefile index ec7dbfca6..599ab39fb 100644 --- a/Makefile +++ b/Makefile @@ -110,10 +110,8 @@ vet: #EXHELP Run go vet against code. test: manifests generate fmt vet test-unit test-e2e #HELP Run all tests. .PHONY: e2e -FOCUS := $(if $(TEST),-v -focus "$(TEST)") -E2E_FLAGS ?= "" e2e: $(SETUP_ENVTEST) #EXHELP Run the e2e tests. - eval $$($(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION)) && go test -tags $(GO_BUILD_TAGS) -v ./test/e2e/... + go test -tags $(GO_BUILD_TAGS) -v ./test/e2e/... export REG_PKG_NAME=registry-operator export PLAIN_PKG_NAME=plain-operator @@ -121,7 +119,7 @@ export CATALOG_IMG=${E2E_REGISTRY_NAME}.${E2E_REGISTRY_NAMESPACE}.svc:5000/test- .PHONY: test-ext-dev-e2e test-ext-dev-e2e: $(SETUP_ENVTEST) $(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} - eval $$($(SETUP_ENVTEST) use -p env $(ENVTEST_VERSION)) && go test -tags $(GO_BUILD_TAGS) -v ./test/extension-developer-e2e/... + go test -tags $(GO_BUILD_TAGS) -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/') @@ -153,7 +151,19 @@ e2e-coverage: .PHONY: kind-load kind-load: $(KIND) #EXHELP Loads the currently constructed image onto the cluster. +ifeq ($(CONTAINER_RUNTIME),podman) + @echo "Using Podman" + @DEPLOY_TEMPLATE_IMG_NAME=quay.io/operator-framework/operator-controller:devel; \ + TMP_FILE=temp_image.tar; \ + podman tag $(IMG) $$DEPLOY_TEMPLATE_IMG_NAME; \ + echo "Saving image to temporary file $$TMP_FILE"; \ + podman save $$DEPLOY_TEMPLATE_IMG_NAME -o $$TMP_FILE; \ + $(KIND) load image-archive $$TMP_FILE --name $(KIND_CLUSTER_NAME); \ + rm $$TMP_FILE +else + @echo "Using Docker" $(KIND) load docker-image $(IMG) --name $(KIND_CLUSTER_NAME) +endif kind-deploy: export MANIFEST="./operator-controller.yaml" kind-deploy: manifests $(KUSTOMIZE) #EXHELP Install controller and dependencies onto the kind cluster. @@ -163,7 +173,8 @@ kind-deploy: manifests $(KUSTOMIZE) #EXHELP Install controller and dependencies .PHONY: kind-cluster kind-cluster: $(KIND) #EXHELP Standup a kind cluster. -$(KIND) delete cluster --name ${KIND_CLUSTER_NAME} - $(KIND) create cluster --name ${KIND_CLUSTER_NAME} --image ${KIND_CLUSTER_IMAGE} + # kind-config.yaml can be deleted after upgrading to Kubernetes 1.30 + $(KIND) create cluster --name ${KIND_CLUSTER_NAME} --image ${KIND_CLUSTER_IMAGE} --config ./kind-config.yaml $(KIND) export kubeconfig --name ${KIND_CLUSTER_NAME} .PHONY: kind-clean @@ -216,7 +227,7 @@ run: docker-build kind-cluster kind-load kind-deploy #HELP Build the operator-co .PHONY: docker-build docker-build: build-linux #EXHELP Build docker image for operator-controller with GOOS=linux and local GOARCH. - docker build -t ${IMG} -f Dockerfile ./bin/linux + $(CONTAINER_RUNTIME) build -t ${IMG} -f Dockerfile ./bin/linux #SECTION Release diff --git a/api/v1alpha1/clusterextension_types.go b/api/v1alpha1/clusterextension_types.go index 0d7f0b83d..e46399f1d 100644 --- a/api/v1alpha1/clusterextension_types.go +++ b/api/v1alpha1/clusterextension_types.go @@ -22,6 +22,11 @@ import ( "github.com/operator-framework/operator-controller/internal/conditionsets" ) +var ( + ClusterExtensionGVK = SchemeBuilder.GroupVersion.WithKind("ClusterExtension") + ClusterExtensionKind = ClusterExtensionGVK.Kind +) + type UpgradeConstraintPolicy string const ( @@ -75,8 +80,11 @@ type ClusterExtensionSpec struct { const ( // TODO(user): add more Types, here and into init() - TypeInstalled = "Installed" - TypeResolved = "Resolved" + TypeInstalled = "Installed" + TypeResolved = "Resolved" + TypeHasValidBundle = "HasValidBundle" + TypeHealthy = "Healthy" + // TypeDeprecated is a rollup condition that is present when // any of the deprecated conditions are present. TypeDeprecated = "Deprecated" @@ -84,6 +92,8 @@ const ( TypeChannelDeprecated = "ChannelDeprecated" TypeBundleDeprecated = "BundleDeprecated" + ReasonErrorGettingClient = "ErrorGettingClient" + ReasonBundleLoadFailed = "BundleLoadFailed" ReasonBundleLookupFailed = "BundleLookupFailed" ReasonInstallationFailed = "InstallationFailed" ReasonInstallationStatusUnknown = "InstallationStatusUnknown" @@ -93,6 +103,9 @@ const ( ReasonResolutionUnknown = "ResolutionUnknown" ReasonSuccess = "Success" ReasonDeprecated = "Deprecated" + ReasonErrorGettingReleaseState = "ErrorGettingReleaseState" + ReasonUpgradeFailed = "UpgradeFailed" + ReasonCreateDynamicWatchFailed = "CreateDynamicWatchFailed" ) func init() { @@ -100,6 +113,8 @@ func init() { conditionsets.ConditionTypes = append(conditionsets.ConditionTypes, TypeInstalled, TypeResolved, + TypeHasValidBundle, + TypeHealthy, TypeDeprecated, TypePackageDeprecated, TypeChannelDeprecated, @@ -116,6 +131,11 @@ func init() { ReasonInvalidSpec, ReasonSuccess, ReasonDeprecated, + ReasonErrorGettingReleaseState, + ReasonUpgradeFailed, + ReasonCreateDynamicWatchFailed, + ReasonBundleLoadFailed, + ReasonErrorGettingClient, ) } diff --git a/cmd/manager/main.go b/cmd/manager/main.go index f782f461a..ef188f857 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -18,7 +18,9 @@ package main import ( "flag" + "fmt" "net/http" + "net/url" "os" "time" @@ -35,13 +37,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/metrics/server" catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/deppy/pkg/deppy/solver" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "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/controllers" + "github.com/operator-framework/operator-controller/internal/rukpak/handler" + "github.com/operator-framework/operator-controller/internal/rukpak/source" + "github.com/operator-framework/operator-controller/internal/rukpak/storage" + "github.com/operator-framework/operator-controller/internal/rukpak/util" "github.com/operator-framework/operator-controller/pkg/features" ) @@ -63,17 +69,25 @@ func init() { func main() { var ( - metricsAddr string - enableLeaderElection bool - probeAddr string - cachePath string + metricsAddr string + enableLeaderElection bool + probeAddr string + cachePath string + httpExternalAddr string + systemNamespace string + unpackImage string + provisionerStorageDirectory 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(&httpExternalAddr, "http-external-address", "http://localhost:8080", "The external address at which the http server is reachable.") 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.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") + flag.StringVar(&unpackImage, "unpack-image", util.DefaultUnpackImage, "Configures the container image that gets used to unpack Bundle contents.") + flag.StringVar(&provisionerStorageDirectory, "provisioner-storage-dir", storage.DefaultBundleCacheDir, "The directory that is used to store bundle contents.") opts := zap.Options{ Development: true, } @@ -85,6 +99,7 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts), zap.StacktraceLevel(zapcore.DPanicLevel))) + fmt.Println("set up manager") mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{BindAddress: metricsAddr}, @@ -111,29 +126,60 @@ func main() { cl := mgr.GetClient() catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, &http.Client{Timeout: 10 * time.Second})) - resolver, err := solver.New() + cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), mgr.GetLogger()) if err != nil { - setupLog.Error(err, "unable to create a solver") + setupLog.Error(err, "unable to config for creating helm client") os.Exit(1) } - if err = (&controllers.ClusterExtensionReconciler{ - Client: cl, - BundleProvider: catalogClient, - Scheme: mgr.GetScheme(), - Resolver: resolver, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") + acg, err := helmclient.NewActionClientGetter(cfgGetter) + if err != nil { + setupLog.Error(err, "unable to create helm client") + os.Exit(1) + } + + if systemNamespace == "" { + systemNamespace = util.PodNamespace() + } + + unpacker, err := source.NewDefaultUnpacker(mgr, systemNamespace, unpackImage) + if err != nil { + setupLog.Error(err, "unable to create unpacker") os.Exit(1) } - if err = (&controllers.ExtensionReconciler{ - Client: cl, - BundleProvider: catalogClient, + storageURL, err := url.Parse(fmt.Sprintf("%s/bundles/", httpExternalAddr)) + if err != nil { + setupLog.Error(err, "unable to parse bundle content server URL") + os.Exit(1) + } + + localStorage := &storage.LocalDirectory{ + RootDirectory: provisionerStorageDirectory, + URL: *storageURL, + } + + if err = (&controllers.ClusterExtensionReconciler{ + Client: cl, + ReleaseNamespace: systemNamespace, + BundleProvider: catalogClient, + Scheme: mgr.GetScheme(), + ActionClientGetter: acg, + Unpacker: unpacker, + Storage: localStorage, + Handler: handler.HandlerFunc(handler.HandleClusterExtension), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Extension") + setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") os.Exit(1) } + + // if err = (&controllers.ExtensionReconciler{ + // Client: cl, + // BundleProvider: catalogClient, + // }).SetupWithManager(mgr); err != nil { + // setupLog.Error(err, "unable to create controller", "controller", "Extension") + // os.Exit(1) + // } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index bde0f91a7..04e53a208 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -61,6 +61,8 @@ spec: volumeMounts: - name: cache mountPath: /var/cache + - name: bundle-cache + mountPath: /var/cache/bundles securityContext: allowPrivilegeEscalation: false capabilities: @@ -109,3 +111,5 @@ spec: volumes: - name: cache emptyDir: {} + - name: bundle-cache + emptyDir: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 7b53723b7..9894b7d54 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,12 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' - apiGroups: - catalogd.operatorframework.io resources: @@ -19,16 +25,27 @@ rules: - list - watch - apiGroups: - - core.rukpak.io + - "" + resources: + - configmaps + verbs: + - list + - watch +- apiGroups: + - "" resources: - - bundledeployments + - pods verbs: - create - - get + - delete - list - - patch - - update - watch +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get - apiGroups: - kappctrl.k14s.io resources: diff --git a/go.mod b/go.mod index 0dbd55f2a..38991d944 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,11 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/go-logr/logr v1.4.1 github.com/google/go-cmp v0.6.0 + github.com/nlepage/go-tarfs v1.2.1 + github.com/operator-framework/api v0.23.0 github.com/operator-framework/catalogd v0.12.0 github.com/operator-framework/deppy v0.3.0 + github.com/operator-framework/helm-operator-plugins v0.1.3 github.com/operator-framework/operator-registry v1.39.0 github.com/operator-framework/rukpak v0.19.0 github.com/spf13/pflag v1.0.5 @@ -19,17 +22,26 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a gopkg.in/yaml.v2 v2.4.0 + helm.sh/helm/v3 v3.14.4 k8s.io/api v0.29.3 + k8s.io/apiextensions-apiserver v0.29.3 k8s.io/apimachinery v0.29.3 + k8s.io/cli-runtime v0.29.2 k8s.io/client-go v0.29.3 k8s.io/component-base v0.29.3 k8s.io/utils v0.0.0-20240102154912-e7106e64919e sigs.k8s.io/controller-runtime v0.17.2 + sigs.k8s.io/yaml v1.4.0 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.3.2 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.12.0-rc.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect @@ -37,6 +49,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/continuity v0.4.2 // indirect @@ -48,6 +61,7 @@ require ( github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.1.9 // indirect github.com/containers/storage v1.51.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v25.0.5+incompatible // indirect @@ -55,72 +69,111 @@ require ( github.com/docker/docker v25.0.5+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-air/gini v1.0.4 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-migrate/migrate/v4 v4.17.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/cel-go v0.17.7 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/miekg/dns v1.1.43 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/onsi/gomega v1.32.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc6 // indirect github.com/opencontainers/runc v1.1.12 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect - github.com/operator-framework/api v0.23.0 // indirect + github.com/operator-framework/operator-lib v0.12.0 // indirect github.com/otiai10/copy v1.14.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.47.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rubenv/sql-migrate v1.5.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/vmware-tanzu/carvel-vendir v0.36.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect go.etcd.io/bbolt v1.3.9 // indirect + go.etcd.io/etcd/api/v3 v3.5.12 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect go.opentelemetry.io/otel v1.23.1 // indirect @@ -130,13 +183,15 @@ require ( go.opentelemetry.io/otel/sdk v1.23.1 // indirect go.opentelemetry.io/otel/trace v1.23.1 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.starlark.net v0.0.0-20230612165344-9532f5667272 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect @@ -150,12 +205,14 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.3 // indirect k8s.io/apiserver v0.29.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1 // indirect + k8s.io/kubectl v0.29.2 // indirect + oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 32e8a074d..5ae97b293 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,24 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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= 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/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.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +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/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.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -13,12 +26,17 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 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/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 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/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/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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -30,6 +48,11 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/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.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= @@ -60,6 +83,10 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -85,6 +112,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ 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/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= @@ -96,14 +125,24 @@ github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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/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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-air/gini v1.0.4 h1:lteMAxHKNOAjIqazL/klOJJmxq6YxxSuJ17MnMXny+s= github.com/go-air/gini v1.0.4/go.mod h1:dd8RvT1xcv6N1da33okvBd8DhMh1/A4siGy6ErjTljs= +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.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 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= @@ -113,8 +152,13 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= 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/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.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +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-logr/logr v1.2.2/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= @@ -128,8 +172,20 @@ github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdX github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= 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-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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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/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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= @@ -142,6 +198,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -155,6 +212,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree 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.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -164,6 +223,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -175,6 +235,9 @@ 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/pprof v0.0.0-20230907193218-d3ddc7976beb h1:LCMfzVg3sflxTs4UvuP4D8CkoZnfHLe2qzqgDn/4OHs= github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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/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= @@ -182,6 +245,13 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE 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/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-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/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= @@ -195,10 +265,15 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.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/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.12/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= @@ -207,12 +282,19 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf 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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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.0.0-20210607151042-0d25dc18b62d h1:A2/B900ip/Z20TzkLeGRNy1s6J2HmH9AmGt+dHyqb4I= github.com/joelanford/ignore v0.0.0-20210607151042-0d25dc18b62d/go.mod h1:7HQupe4vyNxMKXmM5DFuwXHsqwMyglcYmZBtlDPIcZ8= 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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/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= @@ -221,6 +303,7 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -229,27 +312,72 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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/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.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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/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/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +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.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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.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.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 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/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +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= 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/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/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nlepage/go-tarfs v1.2.1 h1:o37+JPA+ajllGKSPfy5+YpsNHDjZnAoyfvf5GsUa+Ks= +github.com/nlepage/go-tarfs v1.2.1/go.mod h1:rno18mpMy9aEH1IiJVftFsqPyIpwqSUiAOpJYjlV2NA= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= @@ -269,6 +397,10 @@ github.com/operator-framework/catalogd v0.12.0 h1:Cww+CyowkfTFugB9ZjUDpKvumh2vPe github.com/operator-framework/catalogd v0.12.0/go.mod h1:4lryGtBTVOdqlKR0MaVYnlsSOc7HiagVRVo3J4uIo7E= github.com/operator-framework/deppy v0.3.0 h1:W8wpF0ehcTAdH2WfMyqMPI5Ja0Qv8M5FMO5cXgJvEQ8= github.com/operator-framework/deppy v0.3.0/go.mod h1:EHDxZz8fKGvuymCng3G/Ou7wuX14GaLr0cmf2u29Oog= +github.com/operator-framework/helm-operator-plugins v0.1.3 h1:nwl9K1Pq0NZmanpEF/DYO00S7QO/iAmEdRIuLROrYpk= +github.com/operator-framework/helm-operator-plugins v0.1.3/go.mod h1:f/AR6r2DiSRK5zv9MD+NgWbayP6qDbQMw+unFuw0rPQ= +github.com/operator-framework/operator-lib v0.12.0 h1:OzpMU5N7mvFgg/uje8FUUeD24Ahq64R6TdN25uswCYA= +github.com/operator-framework/operator-lib v0.12.0/go.mod h1:ClpLUI7hctEF7F5DBe/kg041dq/4NLR7XC5tArY7bG4= 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/rukpak v0.19.0 h1:8cW43z4jsvARlsmj2eum5bAsZEvSxqDwfMW3dSq1zq8= @@ -277,20 +409,35 @@ 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +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 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.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_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= -github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +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.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= @@ -300,14 +447,29 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb 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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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.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.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= @@ -318,9 +480,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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.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.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= @@ -338,13 +504,22 @@ github.com/vmware-tanzu/carvel-kapp-controller v0.50.2/go.mod h1:dcE+zg3cMl6CWRW github.com/vmware-tanzu/carvel-vendir v0.36.0 h1:F9FNk2YysC6DlUDP2Nl2ynsv6JH8S0FYT4OK6HrRco0= github.com/vmware-tanzu/carvel-vendir v0.36.0/go.mod h1:rPGI/zItMK4QgLRpLix2aykoYufavHyKqqLTONXb2uE= 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= +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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= -go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= @@ -385,6 +560,8 @@ go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6 go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= 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.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.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -393,14 +570,16 @@ 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -414,9 +593,11 @@ 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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -424,6 +605,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -431,41 +613,53 @@ 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/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-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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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.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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/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-20210616094352-59db8d763f22/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.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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +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.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -523,6 +717,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -532,6 +727,7 @@ 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.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= @@ -544,6 +740,8 @@ 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= +helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= 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/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= @@ -554,6 +752,8 @@ k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= k8s.io/apiserver v0.29.3 h1:xR7ELlJ/BZSr2n4CnD3lfA4gzFivh0wwfNfz9L0WZcE= k8s.io/apiserver v0.29.3/go.mod h1:hrvXlwfRulbMbBgmWRQlFru2b/JySDpmzvQwwk4GUOs= +k8s.io/cli-runtime v0.29.2 h1:smfsOcT4QujeghsNjECKN3lwyX9AwcFU0nvJ7sFN3ro= +k8s.io/cli-runtime v0.29.2/go.mod h1:KLisYYfoqeNfO+MkTWvpqIyb1wpJmmFJhioA0xd4MW8= 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/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= @@ -562,14 +762,22 @@ k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1 h1:rtdnaWfP40MTKv7izH81gkWpZB45pZrwIxyZdPSn1mI= k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kubectl v0.29.2 h1:uaDYaBhumvkwz0S2XHt36fK0v5IdNgL7HyUniwb2IUo= +k8s.io/kubectl v0.29.2/go.mod h1:BhizuYBGcKaHWyq+G7txGw2fXg576QbPrrnQdQDZgqI= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/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= 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/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= 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.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/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/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= diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index b71baaf2c..6fe9ca738 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -17,54 +17,93 @@ limitations under the License. package controllers import ( + "bytes" "context" + "errors" "fmt" + "io" + "sort" "strings" + "sync" + mmsemver "github.com/Masterminds/semver/v3" + bsemver "github.com/blang/semver/v4" "github.com/go-logr/logr" + "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/api/equality" + apierrors "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/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" + crhandler "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/solver" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" "github.com/operator-framework/operator-registry/alpha/declcfg" rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" - olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" + catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" + catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" + rukpakapi "github.com/operator-framework/operator-controller/internal/rukpak/api" + "github.com/operator-framework/operator-controller/internal/rukpak/handler" + helmpredicate "github.com/operator-framework/operator-controller/internal/rukpak/helm-operator-plugins/predicate" + rukpaksource "github.com/operator-framework/operator-controller/internal/rukpak/source" + "github.com/operator-framework/operator-controller/internal/rukpak/storage" + "github.com/operator-framework/operator-controller/internal/rukpak/util" ) // ClusterExtensionReconciler reconciles a ClusterExtension object type ClusterExtensionReconciler struct { client.Client - BundleProvider BundleProvider - Scheme *runtime.Scheme - Resolver *solver.Solver + ReleaseNamespace string + BundleProvider BundleProvider + Unpacker rukpaksource.Unpacker + ActionClientGetter helmclient.ActionClientGetter + Storage storage.Storage + Handler handler.Handler + Scheme *runtime.Scheme + dynamicWatchMutex sync.RWMutex + dynamicWatchGVKs map[schema.GroupVersionKind]struct{} + controller crcontroller.Controller + cache cache.Cache } //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch //+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:groups=core.rukpak.io,resources=bundledeployments,verbs=get;list;watch;create;update;patch +//+kubebuilder:rbac:groups=core,resources=pods,verbs=list;watch;create;delete +//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=list;watch +//+kubebuilder:rbac:groups=core,resources=pods/log,verbs=get +//+kubebuilder:rbac:groups=*,resources=*,verbs=* //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogs,verbs=list;watch //+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogmetadata,verbs=list;watch func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + fmt.Println("start reconciling") + l := log.FromContext(ctx).WithName("operator-controller") l.V(1).Info("starting") defer l.V(1).Info("ending") @@ -76,6 +115,9 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req reconciledExt := existingExt.DeepCopy() res, reconcileErr := r.reconcile(ctx, reconciledExt) + if reconcileErr != nil { + return ctrl.Result{}, reconcileErr + } // Do checks before any Update()s, as Update() may modify the resource structure! updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) @@ -116,152 +158,177 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool { // //nolint:unparam func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) { - // gather vars for resolution - vars, err := r.variables(ctx) - if err != nil { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted due to failure to gather data for resolution", ext.GetGeneration()) - ext.Status.ResolvedBundle = nil - setResolvedStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted due to failure to gather data for resolution", ext.GetGeneration()) - return ctrl.Result{}, err - } - // run resolution - selection, err := r.Resolver.Solve(vars) + fmt.Println("reconciling!!!") + bundle, err := r.resolve(ctx, *ext) if err != nil { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted as resolution failed", ext.GetGeneration()) - ext.Status.ResolvedBundle = nil - setResolvedStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as resolution failed", ext.GetGeneration()) - return ctrl.Result{}, err + // set right statuses + return ctrl.Result{}, fmt.Errorf("error resolving: %v", err) } - // lookup the bundle in the solution that corresponds to the - // ClusterExtension's desired package name. - bundle, err := r.bundleFromSolution(selection, ext.Spec.PackageName) + bundleVersion, err := bundle.Version() if err != nil { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted as resolution failed", ext.GetGeneration()) - ext.Status.ResolvedBundle = nil - setResolvedStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as resolution failed", ext.GetGeneration()) - return ctrl.Result{}, err + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", "unable to get resolved bundle version", err), ext.Generation) + return ctrl.Result{}, fmt.Errorf("error bundleVersion: %v", err) } + fmt.Printf("bundle Version %q", bundleVersion) + // Now we can set the Resolved Condition, and the resolvedBundleSource field to the bundle.Image value. ext.Status.ResolvedBundle = bundleMetadataFor(bundle) setResolvedStatusConditionSuccess(&ext.Status.Conditions, fmt.Sprintf("resolved to %q", bundle.Image), ext.GetGeneration()) - // TODO: Question - Should we set the deprecation statuses after we have successfully resolved instead of after a successful installation? + // Unpack contents into a fs based on the bundle. + // Considering only image source. - mediaType, err := bundle.MediaType() + // Generate a BundleSource, and then pass this and the ClusterExtension to Unpack + bs := r.GenerateExpectedBundleSource(bundle.Image) + unpackResult, err := r.Unpacker.Unpack(ctx, bs, ext) if err != nil { - setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration()) - return ctrl.Result{}, err + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, fmt.Errorf("source bundle content: %v", err)) + } + + fmt.Println("unpack state", unpackResult.State) + switch unpackResult.State { + case rukpaksource.StatePending: + updateStatusUnpackPending(&ext.Status, unpackResult) + // There must be a limit to number of entries if status is stuck at + // unpack pending. + return ctrl.Result{}, nil + case rukpaksource.StateUnpacking: + updateStatusUnpacking(&ext.Status, unpackResult) + return ctrl.Result{}, nil + case rukpaksource.StateUnpacked: + if err := r.Storage.Store(ctx, ext, unpackResult.Bundle); err != nil { + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, fmt.Errorf("persist bundle content: %v", err)) + } + contentURL, err := r.Storage.URLFor(ctx, ext) + if err != nil { + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, fmt.Errorf("get content URL: %v", err)) + } + updateStatusUnpacked(&ext.Status, unpackResult, contentURL) + default: + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, fmt.Errorf("unknown unpack state %q: %v", unpackResult.State, err)) } - bundleProvisioner, err := mapBundleMediaTypeToBundleProvisioner(mediaType) + + bundleFS, err := r.Storage.Load(ctx, ext) if err != nil { - setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration()) + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionFalse, + Reason: ocv1alpha1.ReasonBundleLoadFailed, + Message: err.Error(), + }) return ctrl.Result{}, err } - // Ensure a BundleDeployment exists with its bundle source from the bundle - // image we just looked up in the solution. - dep := r.GenerateExpectedBundleDeployment(*ext, bundle.Image, bundleProvisioner) - if err := r.ensureBundleDeployment(ctx, dep); err != nil { - // originally Reason: ocv1alpha1.ReasonInstallationFailed - ext.Status.InstalledBundle = nil - setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration()) + + chrt, values, err := r.Handler.Handle(ctx, bundleFS, ext) + if err != nil { + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha2.TypeInstalled, + Status: metav1.ConditionFalse, + Reason: rukpakv1alpha2.ReasonInstallFailed, + Message: err.Error(), + }) return ctrl.Result{}, err } - // convert existing unstructured object into bundleDeployment for easier mapping of status. - existingTypedBundleDeployment := &rukpakv1alpha2.BundleDeployment{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(dep.UnstructuredContent(), existingTypedBundleDeployment); err != nil { - // originally Reason: ocv1alpha1.ReasonInstallationStatusUnknown - ext.Status.InstalledBundle = nil - setInstalledStatusConditionUnknown(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration()) + ext.SetNamespace(r.ReleaseNamespace) + ac, err := r.ActionClientGetter.ActionClientFor(ext) + if err != nil { + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonErrorGettingClient, err), ext.Generation) return ctrl.Result{}, err } - // Let's set the proper Installed condition and InstalledBundle field based on the - // existing BundleDeployment object status. - mapBDStatusToInstalledCondition(existingTypedBundleDeployment, ext, bundle) - - SetDeprecationStatus(ext, bundle) - - // set the status of the cluster extension based on the respective bundle deployment status conditions. - return ctrl.Result{}, nil -} + post := &postrenderer{ + labels: map[string]string{ + util.OwnerKindKey: ocv1alpha1.ClusterExtensionKind, + util.OwnerNameKey: ext.GetName(), + util.BundleNameKey: bundle.Name, + util.PackageNameKey: bundle.Package, + util.BundleVersionKey: bundleVersion.String(), + }, + } -func (r *ClusterExtensionReconciler) variables(ctx context.Context) ([]deppy.Variable, error) { - allBundles, err := r.BundleProvider.Bundles(ctx) + rel, state, err := r.getReleaseState(ac, ext, chrt, values, post) if err != nil { - return nil, err - } - clusterExtensionList := ocv1alpha1.ClusterExtensionList{} - if err := r.Client.List(ctx, &clusterExtensionList); err != nil { - return nil, err - } - bundleDeploymentList := rukpakv1alpha2.BundleDeploymentList{} - if err := r.Client.List(ctx, &bundleDeploymentList); err != nil { - return nil, err + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonErrorGettingReleaseState, err), ext.Generation) + return ctrl.Result{}, err } - return GenerateVariables(allBundles, clusterExtensionList.Items, bundleDeploymentList.Items) -} - -func mapBDStatusToInstalledCondition(existingTypedBundleDeployment *rukpakv1alpha2.BundleDeployment, ext *ocv1alpha1.ClusterExtension, bundle *catalogmetadata.Bundle) { - bundleDeploymentReady := apimeta.FindStatusCondition(existingTypedBundleDeployment.Status.Conditions, rukpakv1alpha2.TypeInstalled) - if bundleDeploymentReady == nil { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionUnknown(&ext.Status.Conditions, "bundledeployment status is unknown", ext.GetGeneration()) - return + switch state { + case stateNeedsInstall: + rel, err = ac.Install(ext.GetName(), r.ReleaseNamespace, chrt, values, func(install *action.Install) error { + install.CreateNamespace = false + install.Labels = map[string]string{util.BundleNameKey: bundle.Name, util.PackageNameKey: bundle.Package, util.BundleVersionKey: bundleVersion.String()} + return nil + }, helmclient.AppendInstallPostRenderer(post)) + if err != nil { + if isResourceNotFoundErr(err) { + err = errRequiredResourceNotFound{err} + } + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonInstallationFailed, err), ext.Generation) + return ctrl.Result{}, err + } + case stateNeedsUpgrade: + rel, err = ac.Upgrade(ext.GetName(), r.ReleaseNamespace, chrt, values, helmclient.AppendUpgradePostRenderer(post)) + if err != nil { + if isResourceNotFoundErr(err) { + err = errRequiredResourceNotFound{err} + } + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonUpgradeFailed, err), ext.Generation) + return ctrl.Result{}, err + } + case stateUnchanged: + if err := ac.Reconcile(rel); err != nil { + if isResourceNotFoundErr(err) { + err = errRequiredResourceNotFound{err} + } + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonResolutionFailed, err), ext.Generation) + return ctrl.Result{}, err + } + default: + return ctrl.Result{}, fmt.Errorf("unexpected release state %q", state) } - if bundleDeploymentReady.Status != metav1.ConditionTrue { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionFailed( - &ext.Status.Conditions, - fmt.Sprintf("bundledeployment not ready: %s", bundleDeploymentReady.Message), - ext.GetGeneration(), - ) - return + relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) + if err != nil { + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonCreateDynamicWatchFailed, err), ext.Generation) + return ctrl.Result{}, err } - installedBundle := bundleMetadataFor(bundle) - bundleDeploymentSource := existingTypedBundleDeployment.Spec.Source - switch bundleDeploymentSource.Type { - case rukpakv1alpha2.SourceTypeImage: - ext.Status.InstalledBundle = installedBundle - setInstalledStatusConditionSuccess( - &ext.Status.Conditions, - fmt.Sprintf("installed from %q", bundleDeploymentSource.Image.Ref), - ext.GetGeneration(), - ) - case rukpakv1alpha2.SourceTypeGit: - ext.Status.InstalledBundle = installedBundle - resource := bundleDeploymentSource.Git.Repository + "@" + bundleDeploymentSource.Git.Ref.Commit - setInstalledStatusConditionSuccess( - &ext.Status.Conditions, - fmt.Sprintf("installed from %q", resource), - ext.GetGeneration(), - ) - default: - setInstalledStatusConditionUnknown( - &ext.Status.Conditions, - fmt.Sprintf("unknown bundledeployment source type %q", bundleDeploymentSource.Type), - ext.GetGeneration(), - ) + for _, obj := range relObjects { + uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonCreateDynamicWatchFailed, err), ext.Generation) + return ctrl.Result{}, err + } + + unstructuredObj := &unstructured.Unstructured{Object: uMap} + if err := func() error { + r.dynamicWatchMutex.Lock() + defer r.dynamicWatchMutex.Unlock() + + _, isWatched := r.dynamicWatchGVKs[unstructuredObj.GroupVersionKind()] + if !isWatched { + if err := r.controller.Watch( + source.Kind(r.cache, unstructuredObj), + crhandler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), ext, crhandler.OnlyControllerOwner()), + helmpredicate.DependentPredicateFuncs()); err != nil { + return err + } + r.dynamicWatchGVKs[unstructuredObj.GroupVersionKind()] = struct{}{} + } + return nil + }(); err != nil { + setInstalledAndHealthyFalse(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonCreateDynamicWatchFailed, err), ext.Generation) + return ctrl.Result{}, err + } } + setInstalledStatusConditionSuccess(&ext.Status.Conditions, fmt.Sprintf("Instantiated bundle %s successfully", ext.GetName()), ext.Generation) + + // set the status of the cluster extension based on the respective bundle deployment status conditions. + return ctrl.Result{}, nil } // setDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension @@ -346,17 +413,13 @@ func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundle *catalogmetad } } -func (r *ClusterExtensionReconciler) bundleFromSolution(selection []deppy.Variable, packageName string) (*catalogmetadata.Bundle, error) { - for _, variable := range selection { - switch v := variable.(type) { - case *olmvariables.BundleVariable: - bundlePkgName := v.Bundle().Package - if packageName == bundlePkgName { - return v.Bundle(), nil - } - } +func (r *ClusterExtensionReconciler) GenerateExpectedBundleSource(bundlePath string) *rukpakapi.BundleSource { + return &rukpakapi.BundleSource{ + Type: rukpakapi.SourceTypeImage, + Image: rukpakapi.ImageSource{ + Ref: bundlePath, + }, } - return nil, fmt.Errorf("bundle for package %q not found in solution", packageName) } func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alpha1.ClusterExtension, bundlePath string, bundleProvisioner string) *unstructured.Unstructured { @@ -405,74 +468,75 @@ func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alph // SetupWithManager sets up the controller with the Manager. func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error { - err := ctrl.NewControllerManagedBy(mgr). + controller, err := ctrl.NewControllerManagedBy(mgr). For(&ocv1alpha1.ClusterExtension{}). Watches(&catalogd.Catalog{}, - handler.EnqueueRequestsFromMapFunc(clusterExtensionRequestsForCatalog(mgr.GetClient(), mgr.GetLogger()))). - Owns(&rukpakv1alpha2.BundleDeployment{}). - Complete(r) + crhandler.EnqueueRequestsFromMapFunc(clusterExtensionRequestsForCatalog(mgr.GetClient(), mgr.GetLogger()))). + Watches(&corev1.Pod{}, mapOwneeToOwnerHandler(mgr.GetClient(), mgr.GetLogger(), &ocv1alpha1.ClusterExtension{})). + Build(r) if err != nil { return err } + r.controller = controller + r.cache = mgr.GetCache() return nil } -func (r *ClusterExtensionReconciler) ensureBundleDeployment(ctx context.Context, desiredBundleDeployment *unstructured.Unstructured) error { - // TODO: what if there happens to be an unrelated BD with the same name as the ClusterExtension? - // we should probably also check to see if there's an owner reference and/or a label set - // that we expect only to ever be used by the operator-controller. That way, we don't - // automatically and silently adopt and change a BD that the user doens't intend to be - // owned by the ClusterExtension. - existingBundleDeployment, err := r.existingBundleDeploymentUnstructured(ctx, desiredBundleDeployment.GetName()) - if client.IgnoreNotFound(err) != nil { - return err - } - - // If the existing BD already has everything that the desired BD has, no need to contact the API server. - // Make sure the status of the existingBD from the server is as expected. - if equality.Semantic.DeepDerivative(desiredBundleDeployment, existingBundleDeployment) { - *desiredBundleDeployment = *existingBundleDeployment - return nil - } +func mapOwneeToOwnerHandler(cl client.Client, log logr.Logger, owner client.Object) crhandler.EventHandler { + return crhandler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + ownerGVK, err := apiutil.GVKForObject(owner, cl.Scheme()) + if err != nil { + log.Error(err, "map ownee to owner: lookup GVK for owner") + return nil + } + owneeGVK, err := apiutil.GVKForObject(obj, cl.Scheme()) + if err != nil { + log.Error(err, "map ownee to owner: lookup GVK for ownee") + return nil + } - return r.Client.Patch(ctx, desiredBundleDeployment, client.Apply, client.ForceOwnership, client.FieldOwner("operator-controller")) -} + type ownerInfo struct { + key types.NamespacedName + gvk schema.GroupVersionKind + } + var oi *ownerInfo -func (r *ClusterExtensionReconciler) existingBundleDeploymentUnstructured(ctx context.Context, name string) (*unstructured.Unstructured, error) { - existingBundleDeployment := &rukpakv1alpha2.BundleDeployment{} - err := r.Client.Get(ctx, types.NamespacedName{Name: name}, existingBundleDeployment) - if err != nil { - return nil, err - } - existingBundleDeployment.APIVersion = rukpakv1alpha2.GroupVersion.String() - existingBundleDeployment.Kind = rukpakv1alpha2.BundleDeploymentKind - unstrExistingBundleDeploymentObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(existingBundleDeployment) - if err != nil { - return nil, err - } - return &unstructured.Unstructured{Object: unstrExistingBundleDeploymentObj}, nil -} + for _, ref := range obj.GetOwnerReferences() { + gv, err := schema.ParseGroupVersion(ref.APIVersion) + if err != nil { + log.Error(err, fmt.Sprintf("map ownee to owner: parse ownee's owner reference group version %q", ref.APIVersion)) + return nil + } + refGVK := gv.WithKind(ref.Kind) + if refGVK == ownerGVK && ref.Controller != nil && *ref.Controller { + oi = &ownerInfo{ + key: types.NamespacedName{Name: ref.Name}, + gvk: ownerGVK, + } + break + } + } + if oi == nil { + return nil + } -// mapBundleMediaTypeToBundleProvisioner maps an olm.bundle.mediatype property to a -// rukpak bundle provisioner class name that is capable of unpacking the bundle type -func mapBundleMediaTypeToBundleProvisioner(mediaType string) (string, error) { - switch mediaType { - case catalogmetadata.MediaTypePlain: - return "core-rukpak-io-plain", nil - // To ensure compatibility with bundles created with OLMv0 where the - // olm.bundle.mediatype property doesn't exist, we assume that if the - // property is empty (i.e doesn't exist) that the bundle is one created - // with OLMv0 and therefore should use the registry provisioner - case catalogmetadata.MediaTypeRegistry, "": - return "core-rukpak-io-registry", nil - default: - return "", fmt.Errorf("unknown bundle mediatype: %s", mediaType) - } + if err := cl.Get(ctx, oi.key, owner); client.IgnoreNotFound(err) != nil { + log.Info("map ownee to owner: get owner", + "ownee", client.ObjectKeyFromObject(obj), + "owneeKind", owneeGVK, + "owner", oi.key, + "ownerKind", oi.gvk, + "error", err.Error(), + ) + return nil + } + return []reconcile.Request{{NamespacedName: oi.key}} + }) } // Generate reconcile requests for all cluster extensions affected by a catalog change -func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) handler.MapFunc { +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 := ocv1alpha1.ClusterExtensionList{} @@ -493,3 +557,194 @@ func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) han return requests } } + +func (r *ClusterExtensionReconciler) resolve(ctx context.Context, clusterExtension ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + allBundles, err := r.BundleProvider.Bundles(ctx) + if err != nil { + return nil, fmt.Errorf("error listing bundles: %v", err) + } + + // TODO: change clusterExtension spec to contain a source field. + packageName := clusterExtension.Spec.PackageName + channelName := clusterExtension.Spec.Channel + versionRange := clusterExtension.Spec.Version + + predicates := []catalogfilter.Predicate[catalogmetadata.Bundle]{ + catalogfilter.WithPackageName(packageName), + } + + if channelName != "" { + predicates = append(predicates, catalogfilter.InChannel(channelName)) + } + + if versionRange != "" { + vr, err := mmsemver.NewConstraint(versionRange) + if err != nil { + return nil, fmt.Errorf("invalid version range %q: %w", versionRange, err) + } + predicates = append(predicates, catalogfilter.InMastermindsSemverRange(vr)) + } + + var installedVersion string + // Do not include bundle versions older than currently installed unless UpgradeConstraintPolicy = 'Ignore' + if clusterExtension.Spec.UpgradeConstraintPolicy != ocv1alpha1.UpgradeConstraintPolicyIgnore { + installedVersionSemver, err := r.getInstalledVersion(clusterExtension) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("err: %v", err) + } + if installedVersionSemver != nil { + installedVersion = installedVersionSemver.String() + predicates = append(predicates, catalogfilter.HigherBundleVersion(installedVersionSemver)) + } + } + + resultSet := catalogfilter.Filter(allBundles, catalogfilter.And(predicates...)) + + if len(resultSet) == 0 { + var versionError, channelError, existingVersionError string + if versionRange != "" { + versionError = fmt.Sprintf(" matching version %q", versionRange) + } + if channelName != "" { + channelError = fmt.Sprintf(" in channel %q", channelName) + } + if installedVersion != "" { + existingVersionError = fmt.Sprintf(" which upgrades currently installed version %q", installedVersion) + } + return nil, fmt.Errorf("no package %q%s%s%s found", packageName, versionError, channelError, existingVersionError) + } + + sort.SliceStable(resultSet, func(i, j int) bool { + return catalogsort.ByVersion(resultSet[i], resultSet[j]) + }) + sort.SliceStable(resultSet, func(i, j int) bool { + return catalogsort.ByDeprecated(resultSet[i], resultSet[j]) + }) + return resultSet[0], nil +} + +func (r *ClusterExtensionReconciler) getInstalledVersion(clusterExtension ocv1alpha1.ClusterExtension) (*bsemver.Version, error) { + cl, err := r.ActionClientGetter.ActionClientFor(&clusterExtension) + if err != nil { + return nil, err + } + + // Clarify - Every release will have a unique name as the cluster extension? + // Also filter relases whose owner is the operator controller? + // I think this should work, given we are setting the release Name to the clusterExtension name. + // If not, the other option is to get the Helm secret in the release namespace, list all the releases, + // get the chart annotations. + release, err := cl.Get(clusterExtension.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, fmt.Errorf("error fetching chart: %v", err) + } + if release == nil { + return nil, nil + } + + // TODO: when the chart is created these annotations are to be added. + existingVersion, ok := release.Labels[util.BundleVersionKey] + if !ok { + return nil, fmt.Errorf("release %q: missing bundle version", release.Name) + } + + existingVersionSemver, err := bsemver.New(existingVersion) + if err != nil { + return nil, fmt.Errorf("could not determine bundle version for the chart %q: %w", release.Name, err) + } + return existingVersionSemver, nil +} + +type releaseState string + +const ( + stateNeedsInstall releaseState = "NeedsInstall" + stateNeedsUpgrade releaseState = "NeedsUpgrade" + stateUnchanged releaseState = "Unchanged" + stateError releaseState = "Error" +) + +func (r *ClusterExtensionReconciler) getReleaseState(cl helmclient.ActionInterface, obj metav1.Object, chrt *chart.Chart, values chartutil.Values, post *postrenderer) (*release.Release, releaseState, error) { + currentRelease, err := cl.Get(obj.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, stateError, err + } + if errors.Is(err, driver.ErrReleaseNotFound) { + return nil, stateNeedsInstall, nil + } + + desiredRelease, err := cl.Upgrade(obj.GetName(), r.ReleaseNamespace, chrt, values, func(upgrade *action.Upgrade) error { + upgrade.DryRun = true + return nil + }, helmclient.AppendUpgradePostRenderer(post)) + if err != nil { + return currentRelease, stateError, err + } + if desiredRelease.Manifest != currentRelease.Manifest || + currentRelease.Info.Status == release.StatusFailed || + currentRelease.Info.Status == release.StatusSuperseded { + return currentRelease, stateNeedsUpgrade, nil + } + return currentRelease, stateUnchanged, nil +} + +type errRequiredResourceNotFound struct { + error +} + +func (err errRequiredResourceNotFound) Error() string { + return fmt.Sprintf("required resource not found: %v", err.error) +} + +func isResourceNotFoundErr(err error) bool { + var agg utilerrors.Aggregate + if errors.As(err, &agg) { + for _, err := range agg.Errors() { + return isResourceNotFoundErr(err) + } + } + + nkme := &apimeta.NoKindMatchError{} + if errors.As(err, &nkme) { + return true + } + if apierrors.IsNotFound(err) { + return true + } + + // TODO: improve NoKindMatchError matching + // An error that is bubbled up from the k8s.io/cli-runtime library + // does not wrap meta.NoKindMatchError, so we need to fallback to + // the use of string comparisons for now. + return strings.Contains(err.Error(), "no matches for kind") +} + +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, fmt.Errorf("error decoding objeccts %v", err) + } + obj.SetLabels(util.MergeMaps(obj.GetLabels(), p.labels)) + b, err := obj.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("error marshalling: %v", err) + } + buf.Write(b) + } + if p.cascade != nil { + return p.cascade.Run(&buf) + } + return &buf, nil +} diff --git a/internal/controllers/clusterextension_status.go b/internal/controllers/clusterextension_status.go new file mode 100644 index 000000000..f3ab31c5b --- /dev/null +++ b/internal/controllers/clusterextension_status.go @@ -0,0 +1,75 @@ +/* +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 ( + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + rukpakapi "github.com/operator-framework/operator-controller/internal/rukpak/api" + "github.com/operator-framework/operator-controller/internal/rukpak/source" +) + +func updateStatusUnpackFailing(status *ocv1alpha1.ClusterExtensionStatus, err error) error { + status.ResolvedBundle = nil + status.InstalledBundle = nil + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakapi.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakapi.ReasonUnpackFailed, + Message: err.Error(), + }) + return err +} + +// TODO: verify if we need to update the installBundle status or leave it as is. +func updateStatusUnpackPending(status *ocv1alpha1.ClusterExtensionStatus, result *source.Result) { + status.ResolvedBundle = nil + status.InstalledBundle = nil + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakapi.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakapi.ReasonUnpackPending, + Message: result.Message, + }) +} + +// TODO: verify if we need to update the installBundle status or leave it as is. +func updateStatusUnpacking(status *ocv1alpha1.ClusterExtensionStatus, result *source.Result) { + status.ResolvedBundle = nil + status.InstalledBundle = nil + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakapi.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakapi.ReasonUnpacking, + Message: result.Message, + }) +} + +func updateStatusUnpacked(status *ocv1alpha1.ClusterExtensionStatus, result *source.Result, contentURL string) { + // TODO: Expose content URL through CE status. + status.ResolvedBundle = &ocv1alpha1.BundleMetadata{ + Name: result.ResolvedSource.Image.Ref, + } + meta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakapi.TypeUnpacked, + Status: metav1.ConditionTrue, + Reason: rukpakapi.ReasonUnpackSuccessful, + Message: result.Message, + }) +} diff --git a/internal/controllers/common_controller.go b/internal/controllers/common_controller.go index fd721f469..a19b0e699 100644 --- a/internal/controllers/common_controller.go +++ b/internal/controllers/common_controller.go @@ -117,3 +117,22 @@ func setDeprecationStatusesUnknown(conditions *[]metav1.Condition, message strin }) } } + +// setInstalledAndHealthyFalse sets the Installed and if the feature gate is enabled, the Healthy conditions to False, +// and allows to set the Installed condition reason and message. +func setInstalledAndHealthyFalse(conditions *[]metav1.Condition, message string, generation int64) { + conditionTypes := []string{ + ocv1alpha1.TypeInstalled, + ocv1alpha1.TypeHealthy, + } + + for _, conditionType := range conditionTypes { + apimeta.SetStatusCondition(conditions, metav1.Condition{ + Type: conditionType, + Reason: ocv1alpha1.ReasonInstallationFailed, + Status: metav1.ConditionFalse, + Message: message, + ObservedGeneration: generation, + }) + } +} diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index a7707d272..8cfa6c5f8 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -22,20 +22,26 @@ import ( "path/filepath" "testing" + "github.com/go-logr/logr" "github.com/stretchr/testify/require" carvelv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" corev1 "k8s.io/api/core/v1" + "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" + "sigs.k8s.io/controller-runtime/pkg/manager" "github.com/operator-framework/deppy/pkg/deppy/solver" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/controllers" + "github.com/operator-framework/operator-controller/internal/rukpak/source" + "github.com/operator-framework/operator-controller/internal/rukpak/util" testutil "github.com/operator-framework/operator-controller/test/util" ) @@ -47,16 +53,17 @@ func newClient(t *testing.T) client.Client { } func newClientAndReconciler(t *testing.T) (client.Client, *controllers.ClusterExtensionReconciler) { - resolver, err := solver.New() + _, err := solver.New() require.NoError(t, err) cl := newClient(t) fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.ClusterExtensionReconciler{ - Client: cl, - BundleProvider: &fakeCatalogClient, - Scheme: sch, - Resolver: resolver, + Client: cl, + BundleProvider: &fakeCatalogClient, + Scheme: sch, + ActionClientGetter: acg, + Unpacker: unp, } return cl, reconciler } @@ -74,6 +81,8 @@ func newClientAndExtensionReconciler(t *testing.T) (client.Client, *controllers. var ( sch *runtime.Scheme cfg *rest.Config + acg helmclient.ActionClientGetter + unp source.Unpacker ) func TestMain(m *testing.M) { @@ -97,6 +106,17 @@ func TestMain(m *testing.M) { utilruntime.Must(corev1.AddToScheme(sch)) utilruntime.Must(carvelv1alpha1.AddToScheme(sch)) + rm := meta.NewDefaultRESTMapper(nil) + cfgGetter, err := helmclient.NewActionConfigGetter(cfg, rm, logr.Logger{}) + utilruntime.Must(err) + acg, err = helmclient.NewActionClientGetter(cfgGetter) + utilruntime.Must(err) + + mgr, err := manager.New(cfg, manager.Options{}) + utilruntime.Must(err) + unp, err = source.NewDefaultUnpacker(mgr, util.DefaultSystemNamespace, util.DefaultUnpackImage) + utilruntime.Must(err) + code := m.Run() utilruntime.Must(testEnv.Stop()) os.Exit(code) diff --git a/internal/rukpak/api/bundle_types.go b/internal/rukpak/api/bundle_types.go new file mode 100644 index 000000000..dbd035a7b --- /dev/null +++ b/internal/rukpak/api/bundle_types.go @@ -0,0 +1,51 @@ +/* +Copyright 2021. + +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 rukpakapi + +type SourceType string + +const ( + SourceTypeImage SourceType = "image" + + TypeUnpacked = "Unpacked" + + ReasonUnpackPending = "UnpackPending" + ReasonUnpacking = "Unpacking" + ReasonUnpackSuccessful = "UnpackSuccessful" + ReasonUnpackFailed = "UnpackFailed" + ReasonProcessingFinalizerFailed = "ProcessingFinalizerFailed" + + PhasePending = "Pending" + PhaseUnpacking = "Unpacking" + PhaseFailing = "Failing" + PhaseUnpacked = "Unpacked" +) + +type BundleSource struct { + // Type defines the kind of Bundle content being sourced. + Type SourceType `json:"type"` + // Image is the bundle image that backs the content of this bundle. + Image ImageSource `json:"image,omitempty"` + // Git is the git repository that backs the content of this Bundle. +} + +type ImageSource struct { + // Ref contains the reference to a container image containing Bundle contents. + Ref string `json:"ref"` + // ImagePullSecretName contains the name of the image pull secret in the namespace that the provisioner is deployed. + ImagePullSecretName string `json:"pullSecret,omitempty"` +} diff --git a/internal/rukpak/convert/registryv1.go b/internal/rukpak/convert/registryv1.go new file mode 100644 index 000000000..019f1975b --- /dev/null +++ b/internal/rukpak/convert/registryv1.go @@ -0,0 +1,449 @@ +package convert + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + "testing/fstest" + "time" + + 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/runtime" + "k8s.io/apimachinery/pkg/util/sets" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + "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 + CRDs []apiextensionsv1.CustomResourceDefinition + Others []unstructured.Unstructured +} + +type Plain struct { + Objects []client.Object +} + +func RegistryV1ToPlain(rv1 fs.FS, watchNamespaces []string) (fs.FS, error) { + 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 + + var objects []*unstructured.Unstructured + const manifestsDir = "manifests" + + entries, err := fs.ReadDir(rv1, manifestsDir) + if err != nil { + return nil, err + } + for _, e := range entries { + if e.IsDir() { + return nil, fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, filepath.Join(manifestsDir, e.Name())) + } + fileData, err := fs.ReadFile(rv1, filepath.Join(manifestsDir, e.Name())) + if err != nil { + return nil, err + } + + dec := apimachyaml.NewYAMLOrJSONDecoder(bytes.NewReader(fileData), 1024) + for { + obj := unstructured.Unstructured{} + err := dec.Decode(&obj) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("read %q: %v", e.Name(), err) + } + objects = append(objects, &obj) + } + } + + for _, obj := range objects { + obj := obj + switch obj.GetObjectKind().GroupVersionKind().Kind { + case "ClusterServiceVersion": + csv := v1alpha1.ClusterServiceVersion{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &csv); err != nil { + return nil, err + } + reg.CSV = csv + case "CustomResourceDefinition": + crd := apiextensionsv1.CustomResourceDefinition{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &crd); err != nil { + return nil, err + } + reg.CRDs = append(reg.CRDs, crd) + default: + reg.Others = append(reg.Others, *obj) + } + } + + plain, err := Convert(reg, "", watchNamespaces) + if err != nil { + return nil, err + } + + var manifest bytes.Buffer + for _, obj := range plain.Objects { + yamlData, err := yaml.Marshal(obj) + if err != nil { + return nil, err + } + if _, err := fmt.Fprintf(&manifest, "---\n%s\n", string(yamlData)); err != nil { + return nil, err + } + } + + now := time.Now() + plainFS := fstest.MapFS{ + ".": &fstest.MapFile{ + Data: nil, + Mode: fs.ModeDir | 0755, + ModTime: now, + }, + "manifests": &fstest.MapFile{ + Data: nil, + Mode: fs.ModeDir | 0755, + ModTime: now, + }, + "manifests/manifest.yaml": &fstest.MapFile{ + Data: manifest.Bytes(), + Mode: 0644, + ModTime: now, + }, + } + + return plainFS, nil +} + +func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNamespace string, targetNamespaces []string) error { + set := sets.New[string](targetNamespaces...) + switch set.Len() { + case 0: + if supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces)) { + return nil + } + case 1: + if set.Has("") && supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces)) { + return nil + } + 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)) + } + + ns := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{Kind: "Namespace", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{Name: installNamespace}, + } + objs := []client.Object{ns} + 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.CRDs { + 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) + } + + p := &Plain{Objects: objs} + return p, 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/handler/interfaces.go b/internal/rukpak/handler/interfaces.go new file mode 100644 index 000000000..2bdd502af --- /dev/null +++ b/internal/rukpak/handler/interfaces.go @@ -0,0 +1,21 @@ +package handler + +import ( + "context" + "io/fs" + + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + + ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" +) + +type Handler interface { + Handle(context.Context, fs.FS, *ocv1alpha1.ClusterExtension) (*chart.Chart, chartutil.Values, error) +} + +type HandlerFunc func(context.Context, fs.FS, *ocv1alpha1.ClusterExtension) (*chart.Chart, chartutil.Values, error) + +func (f HandlerFunc) Handle(ctx context.Context, fsys fs.FS, bd *ocv1alpha1.ClusterExtension) (*chart.Chart, chartutil.Values, error) { + return f(ctx, fsys, bd) +} diff --git a/internal/rukpak/handler/registry.go b/internal/rukpak/handler/registry.go new file mode 100644 index 000000000..d93ad2e8c --- /dev/null +++ b/internal/rukpak/handler/registry.go @@ -0,0 +1,104 @@ +package handler + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "io/fs" + "path/filepath" + + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + 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/util" +) + +const ( + manifestsDir = "manifests" +) + +func HandleClusterExtension(_ context.Context, fsys fs.FS, ext *ocv1alpha1.ClusterExtension) (*chart.Chart, chartutil.Values, error) { + plainFS, err := convert.RegistryV1ToPlain(fsys, ext.Spec.WatchNamespaces) + if err != nil { + return nil, nil, fmt.Errorf("convert registry+v1 bundle to plain+v0 bundle: %v", err) + } + if err := ValidateBundle(plainFS); err != nil { + return nil, nil, err + } + + chrt, err := chartFromBundle(plainFS) + if err != nil { + return nil, nil, err + } + return chrt, nil, nil +} + +func ValidateBundle(fsys fs.FS) error { + objects, err := getBundleObjects(fsys) + if err != nil { + return fmt.Errorf("get objects from bundle manifests: %v", err) + } + if len(objects) == 0 { + return errors.New("invalid bundle: found zero objects: plain+v0 bundles are required to contain at least one object") + } + return nil +} + +func getBundleObjects(bundleFS fs.FS) ([]client.Object, error) { + entries, err := fs.ReadDir(bundleFS, manifestsDir) + if err != nil { + return nil, err + } + + var bundleObjects []client.Object + for _, e := range entries { + if e.IsDir() { + return nil, fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, filepath.Join(manifestsDir, e.Name())) + } + + manifestObjects, err := getObjects(bundleFS, e) + if err != nil { + return nil, err + } + bundleObjects = append(bundleObjects, manifestObjects...) + } + return bundleObjects, nil +} + +func getObjects(bundle fs.FS, manifest fs.DirEntry) ([]client.Object, error) { + manifestPath := filepath.Join(manifestsDir, manifest.Name()) + manifestReader, err := bundle.Open(manifestPath) + if err != nil { + return nil, err + } + defer manifestReader.Close() + return util.ManifestObjects(manifestReader, manifestPath) +} + +func chartFromBundle(fsys fs.FS) (*chart.Chart, error) { + objects, err := getBundleObjects(fsys) + if err != nil { + return nil, fmt.Errorf("read bundle objects from bundle: %v", err) + } + + chrt := &chart.Chart{ + Metadata: &chart.Metadata{}, + } + for _, obj := range objects { + yamlData, err := yaml.Marshal(obj) + if err != nil { + return nil, err + } + hash := sha256.Sum256(yamlData) + chrt.Templates = append(chrt.Templates, &chart.File{ + Name: fmt.Sprintf("object-%x.yaml", hash[0:8]), + Data: yamlData, + }) + } + return chrt, nil +} diff --git a/internal/rukpak/helm-operator-plugins/predicate/depedent.go b/internal/rukpak/helm-operator-plugins/predicate/depedent.go new file mode 100644 index 000000000..46a3f89a5 --- /dev/null +++ b/internal/rukpak/helm-operator-plugins/predicate/depedent.go @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Operator-SDK 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 predicate + +import ( + "reflect" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/event" + logf "sigs.k8s.io/controller-runtime/pkg/log" + crtpredicate "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +var log = logf.Log.WithName("predicate") + +type GenerationChangedPredicate = crtpredicate.GenerationChangedPredicate + +// DependentPredicateFuncs returns functions defined for filtering events +func DependentPredicateFuncs() crtpredicate.Funcs { + dependentPredicate := crtpredicate.Funcs{ + // We don't need to reconcile dependent resource creation events + // because dependent resources are only ever created during + // reconciliation. Another reconcile would be redundant. + CreateFunc: func(e event.CreateEvent) bool { + o := e.Object.(*unstructured.Unstructured) + log.V(1).Info("Skipping reconciliation for dependent resource creation", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind) + return false + }, + + // Reconcile when a dependent resource is deleted so that it can be + // recreated. + DeleteFunc: func(e event.DeleteEvent) bool { + o := e.Object.(*unstructured.Unstructured) + log.V(1).Info("Reconciling due to dependent resource deletion", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind) + return true + }, + + // Don't reconcile when a generic event is received for a dependent + GenericFunc: func(e event.GenericEvent) bool { + o := e.Object.(*unstructured.Unstructured) + log.V(1).Info("Skipping reconcile due to generic event", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind) + return false + }, + + // Reconcile when a dependent resource is updated, so that it can + // be patched back to the resource managed by the CR, if + // necessary. Ignore updates that only change the status and + // resourceVersion. + UpdateFunc: func(e event.UpdateEvent) bool { + oldObj := e.ObjectOld.(*unstructured.Unstructured).DeepCopy() + newObj := e.ObjectNew.(*unstructured.Unstructured).DeepCopy() + + delete(oldObj.Object, "status") + delete(newObj.Object, "status") + oldObj.SetResourceVersion("") + newObj.SetResourceVersion("") + + if reflect.DeepEqual(oldObj.Object, newObj.Object) { + return false + } + log.V(1).Info("Reconciling due to dependent resource update", "name", newObj.GetName(), "namespace", newObj.GetNamespace(), "apiVersion", newObj.GroupVersionKind().GroupVersion(), "kind", newObj.GroupVersionKind().Kind) + return true + }, + } + + return dependentPredicate +} diff --git a/internal/rukpak/operator-registry/registry.go b/internal/rukpak/operator-registry/registry.go new file mode 100644 index 000000000..aaa34f57e --- /dev/null +++ b/internal/rukpak/operator-registry/registry.go @@ -0,0 +1,23 @@ +// Package registry contains the manually vendored type definitions from +// the operator-framework/operator-registry repository. +package registry + +// AnnotationsFile holds annotation information about a bundle +type AnnotationsFile struct { + // annotations is a list of annotations for a given bundle + Annotations Annotations `json:"annotations" yaml:"annotations"` +} + +// Annotations is a list of annotations for a given bundle +type Annotations struct { + // PackageName is the name of the overall package, ala `etcd`. + PackageName string `json:"operators.operatorframework.io.bundle.package.v1" yaml:"operators.operatorframework.io.bundle.package.v1"` + + // Channels are a comma separated list of the declared channels for the bundle, ala `stable` or `alpha`. + Channels string `json:"operators.operatorframework.io.bundle.channels.v1" yaml:"operators.operatorframework.io.bundle.channels.v1"` + + // DefaultChannelName is, if specified, the name of the default channel for the package. The + // default channel will be installed if no other channel is explicitly given. If the package + // has a single channel, then that channel is implicitly the default. + DefaultChannelName string `json:"operators.operatorframework.io.bundle.channel.default.v1" yaml:"operators.operatorframework.io.bundle.channel.default.v1"` +} diff --git a/internal/rukpak/source/common.go b/internal/rukpak/source/common.go new file mode 100644 index 000000000..100bceffd --- /dev/null +++ b/internal/rukpak/source/common.go @@ -0,0 +1,9 @@ +package source + +import ( + "fmt" +) + +func generateMessage(bundleName string) string { + return fmt.Sprintf("Successfully unpacked the %s Bundle", bundleName) +} diff --git a/internal/rukpak/source/image.go b/internal/rukpak/source/image.go new file mode 100644 index 000000000..7c48170ee --- /dev/null +++ b/internal/rukpak/source/image.go @@ -0,0 +1,272 @@ +package source + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "io/fs" + "strings" + + "github.com/nlepage/go-tarfs" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + applyconfigurationcorev1 "k8s.io/client-go/applyconfigurations/core/v1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + clusterextension "github.com/operator-framework/operator-controller/api/v1alpha1" + rukpakapi "github.com/operator-framework/operator-controller/internal/rukpak/api" + "github.com/operator-framework/operator-controller/internal/rukpak/util" +) + +type Image struct { + Client client.Client + KubeClient kubernetes.Interface + PodNamespace string + UnpackImage string +} + +const imageBundleUnpackContainerName = "bundle" + +func (i *Image) Unpack(ctx context.Context, bs *rukpakapi.BundleSource, ce *clusterextension.ClusterExtension) (*Result, error) { + if bs.Type != rukpakapi.SourceTypeImage { + return nil, fmt.Errorf("bundle source type %q not supported", bs.Type) + } + + pod := &corev1.Pod{} + op, err := i.ensureUnpackPod(ctx, bs, ce, pod) + if err != nil { + return nil, err + } else if op == controllerutil.OperationResultCreated || op == controllerutil.OperationResultUpdated || pod.DeletionTimestamp != nil { + return &Result{State: StatePending}, nil + } + + switch phase := pod.Status.Phase; phase { + case corev1.PodPending: + return pendingImagePodResult(pod), nil + case corev1.PodRunning: + return &Result{State: StateUnpacking}, nil + case corev1.PodFailed: + return nil, i.failedPodResult(ctx, pod) + case corev1.PodSucceeded: + return i.succeededPodResult(ctx, pod) + default: + return nil, i.handleUnexpectedPod(ctx, pod) + } +} + +func (i *Image) ensureUnpackPod(ctx context.Context, bs *rukpakapi.BundleSource, ce *clusterextension.ClusterExtension, pod *corev1.Pod) (controllerutil.OperationResult, error) { + existingPod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: i.PodNamespace, Name: ce.Name}} + if err := i.Client.Get(ctx, client.ObjectKeyFromObject(existingPod), existingPod); client.IgnoreNotFound(err) != nil { + return controllerutil.OperationResultNone, err + } + + podApplyConfig := i.getDesiredPodApplyConfig(bs, ce) + updatedPod, err := i.KubeClient.CoreV1().Pods(i.PodNamespace).Apply(ctx, podApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "rukpak-core"}) + if err != nil { + if !apierrors.IsInvalid(err) { + return controllerutil.OperationResultNone, err + } + if err := i.Client.Delete(ctx, existingPod); err != nil { + return controllerutil.OperationResultNone, err + } + updatedPod, err = i.KubeClient.CoreV1().Pods(i.PodNamespace).Apply(ctx, podApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "rukpak-core"}) + if err != nil { + return controllerutil.OperationResultNone, err + } + } + + // make sure the passed in pod value is updated with the latest + // version of the pod + *pod = *updatedPod + + // compare existingPod to newPod and return an appropriate + // OperatorResult value. + newPod := updatedPod.DeepCopy() + unsetNonComparedPodFields(existingPod, newPod) + if equality.Semantic.DeepEqual(existingPod, newPod) { + return controllerutil.OperationResultNone, nil + } + return controllerutil.OperationResultUpdated, nil +} + +func (i *Image) getDesiredPodApplyConfig(bs *rukpakapi.BundleSource, ce *clusterextension.ClusterExtension) *applyconfigurationcorev1.PodApplyConfiguration { + // TODO (tyslaton): Address unpacker pod allowing root users for image sources + // + // In our current implementation, we are creating a pod that uses the image + // provided by an image source. This pod is not always guaranteed to run as a + // non-root user and thus will fail to initialize if running as root in a PSA + // restricted namespace due to violations. As it currently stands, our compliance + // with PSA is baseline which allows for pods to run as root users. However, + // all RukPak processes and resources, except this unpacker pod for image sources, + // are runnable in a PSA restricted environment. We should consider ways to make + // this PSA definition either configurable or workable in a restricted namespace. + // + // See https://github.com/operator-framework/rukpak/pull/539 for more detail. + containerSecurityContext := applyconfigurationcorev1.SecurityContext(). + WithAllowPrivilegeEscalation(false). + WithCapabilities(applyconfigurationcorev1.Capabilities(). + WithDrop("ALL"), + ) + + // These references need to be based on ClusterExtension... + podApply := applyconfigurationcorev1.Pod(ce.Name, i.PodNamespace). + WithLabels(map[string]string{ + util.OwnerKindKey: ce.Kind, + util.OwnerNameKey: ce.Name, + }). + WithOwnerReferences(v1.OwnerReference(). + WithName(ce.Name). + WithKind(ce.Kind). + WithAPIVersion(ce.APIVersion). + WithUID(ce.UID). + WithController(true). + WithBlockOwnerDeletion(true), + ). + WithSpec(applyconfigurationcorev1.PodSpec(). + WithAutomountServiceAccountToken(false). + WithRestartPolicy(corev1.RestartPolicyNever). + WithInitContainers(applyconfigurationcorev1.Container(). + WithName("install-unpacker"). + WithImage(i.UnpackImage). + WithImagePullPolicy(corev1.PullIfNotPresent). + WithCommand("cp", "-Rv", "/unpack", "/util/bin/unpack"). + WithVolumeMounts(applyconfigurationcorev1.VolumeMount(). + WithName("util"). + WithMountPath("/util/bin"), + ). + WithSecurityContext(containerSecurityContext), + ). + WithContainers(applyconfigurationcorev1.Container(). + WithName(imageBundleUnpackContainerName). + WithImage(bs.Image.Ref). + WithCommand("/bin/unpack", "--bundle-dir", "/"). + WithVolumeMounts(applyconfigurationcorev1.VolumeMount(). + WithName("util"). + WithMountPath("/bin"), + ). + WithSecurityContext(containerSecurityContext), + ). + WithVolumes(applyconfigurationcorev1.Volume(). + WithName("util"). + WithEmptyDir(applyconfigurationcorev1.EmptyDirVolumeSource()), + ). + WithSecurityContext(applyconfigurationcorev1.PodSecurityContext(). + WithRunAsNonRoot(false). + WithSeccompProfile(applyconfigurationcorev1.SeccompProfile(). + WithType(corev1.SeccompProfileTypeRuntimeDefault), + ), + ), + ) + + if bs.Image.ImagePullSecretName != "" { + podApply.Spec = podApply.Spec.WithImagePullSecrets( + applyconfigurationcorev1.LocalObjectReference().WithName(bs.Image.ImagePullSecretName), + ) + } + return podApply +} + +func unsetNonComparedPodFields(pods ...*corev1.Pod) { + for _, p := range pods { + p.APIVersion = "" + p.Kind = "" + p.Status = corev1.PodStatus{} + } +} + +func (i *Image) failedPodResult(ctx context.Context, pod *corev1.Pod) error { + logs, err := i.getPodLogs(ctx, pod) + if err != nil { + return fmt.Errorf("unpack failed: failed to retrieve failed pod logs: %v", err) + } + _ = i.Client.Delete(ctx, pod) + return fmt.Errorf("unpack failed: %v", string(logs)) +} + +func (i *Image) succeededPodResult(ctx context.Context, pod *corev1.Pod) (*Result, error) { + bundleFS, err := i.getBundleContents(ctx, pod) + if err != nil { + return nil, fmt.Errorf("get bundle contents: %v", err) + } + + digest, err := i.getBundleImageDigest(pod) + if err != nil { + return nil, fmt.Errorf("get bundle image digest: %v", err) + } + + resolvedSource := &rukpakapi.BundleSource{ + Type: rukpakapi.SourceTypeImage, + Image: rukpakapi.ImageSource{Ref: digest}, + } + + message := generateMessage("image") + + return &Result{Bundle: bundleFS, ResolvedSource: resolvedSource, State: StateUnpacked, Message: message}, nil +} + +func (i *Image) getBundleContents(ctx context.Context, pod *corev1.Pod) (fs.FS, error) { + bundleData, err := i.getPodLogs(ctx, pod) + if err != nil { + return nil, fmt.Errorf("get bundle contents: %v", err) + } + bd := struct { + Content []byte `json:"content"` + }{} + + if err := json.Unmarshal(bundleData, &bd); err != nil { + return nil, fmt.Errorf("parse bundle data: %v", err) + } + + gzr, err := gzip.NewReader(bytes.NewReader(bd.Content)) + if err != nil { + return nil, fmt.Errorf("read bundle content gzip: %v", err) + } + return tarfs.New(gzr) +} + +func (i *Image) getBundleImageDigest(pod *corev1.Pod) (string, error) { + for _, ps := range pod.Status.ContainerStatuses { + if ps.Name == imageBundleUnpackContainerName && ps.ImageID != "" { + return ps.ImageID, nil + } + } + return "", fmt.Errorf("bundle image digest not found") +} + +func (i *Image) getPodLogs(ctx context.Context, pod *corev1.Pod) ([]byte, error) { + logReader, err := i.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{}).Stream(ctx) + if err != nil { + return nil, fmt.Errorf("get pod logs: %v", err) + } + defer logReader.Close() + buf := &bytes.Buffer{} + if _, err := io.Copy(buf, logReader); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (i *Image) handleUnexpectedPod(ctx context.Context, pod *corev1.Pod) error { + _ = i.Client.Delete(ctx, pod) + return fmt.Errorf("unexpected pod phase: %v", pod.Status.Phase) +} + +func pendingImagePodResult(pod *corev1.Pod) *Result { + var messages []string + for _, cStatus := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) { + if waiting := cStatus.State.Waiting; waiting != nil { + if waiting.Reason == "ErrImagePull" || waiting.Reason == "ImagePullBackOff" { + messages = append(messages, waiting.Message) + } + } + } + return &Result{State: StatePending, Message: strings.Join(messages, "; ")} +} diff --git a/internal/rukpak/source/unpacker.go b/internal/rukpak/source/unpacker.go new file mode 100644 index 000000000..03f65d49c --- /dev/null +++ b/internal/rukpak/source/unpacker.go @@ -0,0 +1,110 @@ +package source + +import ( + "context" + "fmt" + "io/fs" + + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/manager" + + clusterextension "github.com/operator-framework/operator-controller/api/v1alpha1" + rukpakapi "github.com/operator-framework/operator-controller/internal/rukpak/api" +) + +// 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, *rukpakapi.BundleSource, *clusterextension.ClusterExtension) (*Result, 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 *rukpakapi.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 unpacker struct { + sources map[rukpakapi.SourceType]Unpacker +} + +// NewUnpacker returns a new composite Source that unpacks bundles using the source +// mapping provided by the configured sources. +func NewUnpacker(sources map[rukpakapi.SourceType]Unpacker) Unpacker { + return &unpacker{sources: sources} +} + +func (s *unpacker) Unpack(ctx context.Context, bs *rukpakapi.BundleSource, ce *clusterextension.ClusterExtension) (*Result, error) { + source, ok := s.sources[bs.Type] + if !ok { + return nil, fmt.Errorf("cluster extension %q, source type %q not supported", ce.Name, bs.Type) + } + return source.Unpack(ctx, bs, ce) +} + +// NewDefaultUnpacker returns a new composite Source that unpacks bundles using +// a default source mapping with built-in implementations of all of the supported +// source types. +// +// TODO: refactor NewDefaultUnpacker due to growing parameter list +func NewDefaultUnpacker(mgr manager.Manager, namespace, unpackImage string) (Unpacker, error) { + cfg := mgr.GetConfig() + kubeClient, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, err + } + return NewUnpacker(map[rukpakapi.SourceType]Unpacker{ + rukpakapi.SourceTypeImage: &Image{ + Client: mgr.GetClient(), + KubeClient: kubeClient, + PodNamespace: namespace, + UnpackImage: unpackImage, + }, + }), nil +} diff --git a/internal/rukpak/storage/localdir.go b/internal/rukpak/storage/localdir.go new file mode 100644 index 000000000..3c8de4253 --- /dev/null +++ b/internal/rukpak/storage/localdir.go @@ -0,0 +1,88 @@ +package storage + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "net/url" + "os" + "path/filepath" + + "github.com/nlepage/go-tarfs" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-controller/internal/rukpak/util" +) + +var _ Storage = &LocalDirectory{} + +const DefaultBundleCacheDir = "/var/cache/bundles" + +type LocalDirectory struct { + RootDirectory string + URL url.URL +} + +func (s *LocalDirectory) Load(_ context.Context, owner client.Object) (fs.FS, error) { + bundleFile, err := os.Open(s.bundlePath(owner.GetName())) + if err != nil { + return nil, err + } + defer bundleFile.Close() + tarReader, err := gzip.NewReader(bundleFile) + if err != nil { + return nil, err + } + return tarfs.New(tarReader) +} + +func (s *LocalDirectory) Store(_ context.Context, owner client.Object, bundle fs.FS) error { + buf := &bytes.Buffer{} + if err := util.FSToTarGZ(buf, bundle); err != nil { + return fmt.Errorf("convert bundle %q to tar.gz: %v", owner.GetName(), err) + } + + bundleFile, err := os.Create(s.bundlePath(owner.GetName())) + if err != nil { + return err + } + defer bundleFile.Close() + + if _, err := io.Copy(bundleFile, buf); err != nil { + return err + } + return nil +} + +func (s *LocalDirectory) Delete(_ context.Context, owner client.Object) error { + return ignoreNotExist(os.Remove(s.bundlePath(owner.GetName()))) +} + +func (s *LocalDirectory) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + fsys := &util.FilesOnlyFilesystem{FS: os.DirFS(s.RootDirectory)} + http.StripPrefix(s.URL.Path, http.FileServer(http.FS(fsys))).ServeHTTP(resp, req) +} + +func (s *LocalDirectory) URLFor(_ context.Context, owner client.Object) (string, error) { + return fmt.Sprintf("%s%s", s.URL.String(), localDirectoryBundleFile(owner.GetName())), nil +} + +func (s *LocalDirectory) bundlePath(bundleName string) string { + return filepath.Join(s.RootDirectory, localDirectoryBundleFile(bundleName)) +} + +func localDirectoryBundleFile(bundleName string) string { + return fmt.Sprintf("%s.tgz", bundleName) +} + +func ignoreNotExist(err error) error { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err +} diff --git a/internal/rukpak/storage/storage.go b/internal/rukpak/storage/storage.go new file mode 100644 index 000000000..cedc99a11 --- /dev/null +++ b/internal/rukpak/storage/storage.go @@ -0,0 +1,46 @@ +package storage + +import ( + "context" + "io/fs" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Storage interface { + Loader + Storer +} + +type Loader interface { + Load(ctx context.Context, owner client.Object) (fs.FS, error) +} + +type Storer interface { + Store(ctx context.Context, owner client.Object, bundle fs.FS) error + Delete(ctx context.Context, owner client.Object) error + + http.Handler + URLFor(ctx context.Context, owner client.Object) (string, error) +} + +type fallbackLoaderStorage struct { + Storage + fallbackLoader Loader +} + +func WithFallbackLoader(s Storage, fallback Loader) Storage { + return &fallbackLoaderStorage{ + Storage: s, + fallbackLoader: fallback, + } +} + +func (s *fallbackLoaderStorage) Load(ctx context.Context, owner client.Object) (fs.FS, error) { + fsys, err := s.Storage.Load(ctx, owner) + if err != nil { + return s.fallbackLoader.Load(ctx, owner) + } + return fsys, nil +} diff --git a/internal/rukpak/util/fs.go b/internal/rukpak/util/fs.go new file mode 100644 index 000000000..b036ebe3f --- /dev/null +++ b/internal/rukpak/util/fs.go @@ -0,0 +1,90 @@ +package util + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "testing/fstest" +) + +// FilesOnlyFilesystem is an fs.FS implementation that treats non-regular files +// (e.g. directories, symlinks, devices, etc.) as non-existent. The reason for +// this is so that we only serve bundle files. +// +// This treats directories as not found so that the http server does not serve +// HTML directory index responses. +// +// This treats other symlink files as not found so that we prevent HTTP requests +// from escaping the filesystem root. +// +// Lastly, this treats other non-regular files as not found because they are +// out of scope for serving bundle contents. +type FilesOnlyFilesystem struct { + FS fs.FS +} + +func (f *FilesOnlyFilesystem) Open(name string) (fs.File, error) { + file, err := f.FS.Open(name) + if err != nil { + return nil, err + } + stat, err := file.Stat() + if err != nil { + return nil, err + } + if !stat.Mode().IsRegular() { + return nil, os.ErrNotExist + } + return file, nil +} + +// EnsureBaseDirFS ensures that an fs.FS contains a single directory in its root +// This is useful for bundle formats that require a base directory in the root of +// the bundle. +// +// For example, an unpacked Helm chart might have /Chart.yaml, and we'd +// typically assume as the bundle root. However, when helm archives +// contain at the root of the archive: //Chart.yaml. +// +// If the fs.FS already has this structure, EnsureBaseDirFS just returns fsys +// directly. Otherwise, it returns a new fs.FS where the defaultBaseDir is inserted +// at the root, such that fsys appears within defaultBaseDir. +func EnsureBaseDirFS(fsys fs.FS, defaultBaseDir string) (fs.FS, error) { + cleanDefaultBaseDir := filepath.Clean(defaultBaseDir) + if dir, _ := filepath.Split(cleanDefaultBaseDir); dir != "" { + return nil, fmt.Errorf("default base directory %q contains multiple path segments: must be exactly one", defaultBaseDir) + } + rootFSEntries, err := fs.ReadDir(fsys, ".") + if err != nil { + return nil, err + } + if len(rootFSEntries) == 1 && rootFSEntries[0].IsDir() { + return fsys, nil + } + return &baseDirFS{fsys, defaultBaseDir}, nil +} + +type baseDirFS struct { + fsys fs.FS + baseDir string +} + +func (f baseDirFS) Open(name string) (fs.File, error) { + if name == "." { + return fstest.MapFS{f.baseDir: &fstest.MapFile{Mode: fs.ModeDir}}.Open(name) + } + if name == f.baseDir { + return f.fsys.Open(".") + } + basePrefix := f.baseDir + string(os.PathSeparator) + if strings.HasPrefix(name, basePrefix) { + subName := strings.TrimPrefix(name, basePrefix) + if subName == "" { + subName = "." + } + return f.fsys.Open(subName) + } + return nil, fs.ErrNotExist +} diff --git a/internal/rukpak/util/hash.go b/internal/rukpak/util/hash.go new file mode 100644 index 000000000..a46bb3434 --- /dev/null +++ b/internal/rukpak/util/hash.go @@ -0,0 +1,39 @@ +package util + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "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) { + // 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, + // we can see that the serializers are a thin wrapper over json.Marshal for encoding: + // https://github.com/kubernetes/kubernetes/blob/8509ab82b96caa2365552efa08c8ba8baf11c5ec/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go#L216-L247 + // Therefore, we can be confident that using json.Marshal() here will: + // 1. be stable & idempotent - the library sorts keys, etc. + // 2. be germane to our needs - only fields that serialize and are sent to the server + // 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) + } + + // base62(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 +} diff --git a/internal/rukpak/util/labels.go b/internal/rukpak/util/labels.go new file mode 100644 index 000000000..06eb23e77 --- /dev/null +++ b/internal/rukpak/util/labels.go @@ -0,0 +1,12 @@ +package util + +const ( + OwnerKindKey = "olm.operatorframework.io/owner-kind" + OwnerNameKey = "olm.operatorframework.io/owner-name" + + // Helm Secret annotations use the regex `(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?` + // to validate labels. Open to any suggestions, keeping this for now. + PackageNameKey = "olm_operatorframework_io_package_name" + BundleNameKey = "olm_operatorframework_io_bundle_name" + BundleVersionKey = "olm_operatorframework_io_bundle_version" +) diff --git a/internal/rukpak/util/tar.go b/internal/rukpak/util/tar.go new file mode 100644 index 000000000..3226d4ea2 --- /dev/null +++ b/internal/rukpak/util/tar.go @@ -0,0 +1,63 @@ +package util + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "io/fs" + "os" +) + +// FSToTarGZ writes the filesystem represented by fsys to w as a gzipped tar archive. +// This function unsets user and group information in the tar archive so that readers +// of archives produced by this function do not need to account for differences in +// permissions between source and destination filesystems. +func FSToTarGZ(w io.Writer, fsys fs.FS) error { + gzw := gzip.NewWriter(w) + tw := tar.NewWriter(gzw) + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.Type()&os.ModeSymlink != 0 { + return nil + } + info, err := d.Info() + if err != nil { + return fmt.Errorf("get file info for %q: %v", path, err) + } + + h, err := tar.FileInfoHeader(info, "") + if err != nil { + return fmt.Errorf("build tar file info header for %q: %v", path, err) + } + h.Uid = 0 + h.Gid = 0 + h.Uname = "" + h.Gname = "" + h.Name = path + + if err := tw.WriteHeader(h); err != nil { + return fmt.Errorf("write tar header for %q: %v", path, err) + } + if d.IsDir() { + return nil + } + f, err := fsys.Open(path) + if err != nil { + return fmt.Errorf("open file %q: %v", path, err) + } + if _, err := io.Copy(tw, f); err != nil { + return fmt.Errorf("write tar data for %q: %v", path, err) + } + return nil + }); err != nil { + return fmt.Errorf("generate tar.gz from FS: %v", err) + } + if err := tw.Close(); err != nil { + return err + } + return gzw.Close() +} diff --git a/internal/rukpak/util/util.go b/internal/rukpak/util/util.go new file mode 100644 index 000000000..3674253b1 --- /dev/null +++ b/internal/rukpak/util/util.go @@ -0,0 +1,58 @@ +package util + +import ( + "io" + "os" + + "k8s.io/cli-runtime/pkg/resource" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// TODO verify these +const ( + DefaultSystemNamespace = "operator-controller-system" + DefaultUnpackImage = "quay.io/operator-framework/rukpak:main" +) + +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 +} + +// 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 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 +}