diff --git a/.github/workflows/tilt.yaml b/.github/workflows/tilt.yaml index c72842a60..c8dddf98c 100644 --- a/.github/workflows/tilt.yaml +++ b/.github/workflows/tilt.yaml @@ -22,10 +22,6 @@ jobs: - uses: actions/checkout@v4 with: path: operator-controller - - uses: actions/checkout@v4 - with: - repository: operator-framework/rukpak - path: rukpak - uses: actions/checkout@v4 with: repository: operator-framework/catalogd diff --git a/.goreleaser.yml b/.goreleaser.yml index b636ecaf3..759318a72 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,6 +16,19 @@ builds: - arm64 - ppc64le - s390x + - id: unpack + main: ./cmd/unpack/main.go # Update this path to the actual location of your unpack source + binary: bin/unpack + asmflags: "{{ .Env.GO_BUILD_ASMFLAGS }}" + gcflags: "{{ .Env.GO_BUILD_GCFLAGS }}" + ldflags: "{{ .Env.GO_BUILD_LDFLAGS }}" + goos: + - linux + goarch: + - amd64 + - arm64 + - ppc64le + - s390x dockers: - image_templates: - "{{ .Env.IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56561937c..9cf09a98c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,9 +19,8 @@ The user experience captured in the OLM V1 PRD introduces many requirements that - [The Operator-Controller project](https://github.com/operator-framework/operator-controller/), which is the top level component allowing users to specify operators they'd like to install. - [The Catalogd project](https://github.com/operator-framework/catalogd/), which hosts operator content and helps users discover installable content. - [The Deppy project](https://github.com/operator-framework/deppy/), which enables the operator-controller to identify valid installs and upgrades from the list of installable content provided via the Catalogd project. -- [The RukPak project](https://github.com/operator-framework/rukpak/), which facilitates the installation of operators. -Each of the projects listed above have their own governance, release milestones, and release cadence. However, from a technical perspective, the "OLM V1 experience" matches the experienced offered by the operator-controller project, the top level component which introduces dependencies on RukPak, Deppy, and the Catalogd projects. +Each of the projects listed above have their own governance, release milestones, and release cadence. However, from a technical perspective, the "OLM V1 experience" matches the experienced offered by the operator-controller project, the top level component which introduces dependencies on Deppy and the Catalogd projects. ## How do we collaborate @@ -62,7 +61,7 @@ Ongoing or previous Operator-Controller milestones can always be found in the [m ### How are Subproject Issues Tracked? -As discussed earlier, the operator-controller adheres to a microservice architecture, where multiple projects contribute to the overall experience. As such, when designing an operator-controller milestone, the community may need to file an issue against RukPak, Deppy, or Catalogd. Unfortunately, the operator-controller milestone cannot contain issues from one of its subprojects. As such, we've introduced the concept of a "Dependency Issue", described below: +As discussed earlier, the operator-controller adheres to a microservice architecture, where multiple projects contribute to the overall experience. As such, when designing an operator-controller milestone, the community may need to file an issue against Deppy or Catalogd. Unfortunately, the operator-controller milestone cannot contain issues from one of its subprojects. As such, we've introduced the concept of a "Dependency Issue", described below: > Dependency Issues: An issue tracked in a milestone that "points" to an issue in another project with a URL. @@ -72,7 +71,6 @@ Unsure where to submit an issue? - [The Operator-Controller project](https://github.com/operator-framework/operator-controller/), which is the top level component allowing users to specify operators they'd like to install. - [The Catalogd project](https://github.com/operator-framework/catalogd/), which hosts operator content and helps users discover installable content. - [The Deppy project](https://github.com/operator-framework/deppy/), which enables the operator-controller to identify valid installs and upgrades from the list of installable content provided via the Catalogd project. -- [The RukPak project](https://github.com/operator-framework/rukpak/), which facilitates the installation of operators. Don't worry if you accidentally submit an issue against the wrong project, if we notice that an issue would fit better with a separate project we'll move it to the correct repository and mention it in the #olm-dev slack channel. diff --git a/Dockerfile b/Dockerfile index ddaecedfc..be1eb1792 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,21 @@ # Note: This dockerfile does not build the binaries # required and is intended to be built only with the # 'make build' or 'make release' targets. +# Stage 1: +FROM gcr.io/distroless/static:debug-nonroot AS builder + +# Stage 2: FROM gcr.io/distroless/static:nonroot +# Grab the cp binary so we can cp the unpack +# binary to a shared volume in the bundle image (rukpak library needs it) +COPY --from=builder /busybox/cp /cp + WORKDIR / COPY manager manager +COPY unpack unpack EXPOSE 8080 USER 65532:65532 - -ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index 2571eadab..e4fc640af 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ IMG := $(IMAGE_REPO):$(IMAGE_TAG) # Define dependency versions (use go.mod if we also use Go code from dependency) export CERT_MGR_VERSION := v1.9.0 export CATALOGD_VERSION := $(shell go list -mod=mod -m -f "{{.Version}}" github.com/operator-framework/catalogd) -export RUKPAK_VERSION := $(shell go list -mod=mod -m -f "{{.Version}}" github.com/operator-framework/rukpak) export WAIT_TIMEOUT := 60s # By default setup-envtest will write to $XDG_DATA_HOME, or $HOME/.local/share if that is not defined. @@ -165,10 +164,16 @@ e2e-coverage: kind-load: $(KIND) #EXHELP Loads the currently constructed image onto the cluster. $(CONTAINER_RUNTIME) save $(IMG) | $(KIND) load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME) -kind-deploy: export MANIFEST := ./operator-controller.yaml +.PHONY: kind-deploy +kind-deploy: export MANIFEST="./operator-controller.yaml" kind-deploy: manifests $(KUSTOMIZE) #EXHELP Install controller and dependencies onto the kind cluster. $(KUSTOMIZE) build $(KUSTOMIZE_BUILD_DIR) > operator-controller.yaml - envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$RUKPAK_VERSION,$$MANIFEST' < scripts/install.tpl.sh | bash -s + envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$MANIFEST' < scripts/install.tpl.sh | bash -s + +.PHONY: kind-redeploy +kind-redeploy: generate docker-build kind-load kind-deploy #EXHELP Redeploy newly built executables + kubectl delete pod -l control-plane=controller-manager -n $(OPERATOR_CONTROLLER_NAMESPACE) + envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$MANIFEST' < scripts/install.tpl.sh | bash -s .PHONY: kind-cluster kind-cluster: $(KIND) #EXHELP Standup a kind cluster. @@ -215,7 +220,10 @@ export GO_BUILD_FLAGS := export GO_BUILD_LDFLAGS := -s -w \ -X '$(VERSION_PATH).version=$(VERSION)' \ -BUILDCMD = go build $(GO_BUILD_FLAGS) -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/manager ./cmd/manager +BINARIES=manager unpack + +$(BINARIES): + go build $(GO_BUILD_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/$@ ./cmd/$@ .PHONY: build-deps build-deps: manifests generate fmt vet @@ -223,14 +231,14 @@ build-deps: manifests generate fmt vet .PHONY: build go-build-local build: build-deps go-build-local #HELP Build manager binary for current GOOS and GOARCH. Default target. go-build-local: BUILDBIN := bin -go-build-local: - $(BUILDCMD) +go-build-local: $(BINARIES) .PHONY: build-linux go-build-linux build-linux: build-deps go-build-linux #EXHELP Build manager binary for GOOS=linux and local GOARCH. go-build-linux: BUILDBIN := bin/linux -go-build-linux: - GOOS=linux $(BUILDCMD) +go-build-linux: export GOOS=linux +go-build-linux: export GOARCH=amd64 +go-build-linux: $(BINARIES) .PHONY: run run: docker-build kind-cluster kind-load kind-deploy #HELP Build the operator-controller then deploy it into a new kind cluster. @@ -258,7 +266,7 @@ release: $(GORELEASER) #EXHELP Runs goreleaser for the operator-controller. By d quickstart: export MANIFEST := https://github.com/operator-framework/operator-controller/releases/download/$(VERSION)/operator-controller.yaml quickstart: $(KUSTOMIZE) manifests #EXHELP Generate the installation release manifests and scripts. $(KUSTOMIZE) build $(KUSTOMIZE_BUILD_DIR) | sed "s/:devel/:$(VERSION)/g" > operator-controller.yaml - envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$RUKPAK_VERSION,$$MANIFEST' < scripts/install.tpl.sh > install.sh + envsubst '$$CATALOGD_VERSION,$$CERT_MGR_VERSION,$$MANIFEST' < scripts/install.tpl.sh > install.sh ##@ Docs diff --git a/README.md b/README.md index 0f3b30c12..eea4a117c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ OLM v1 is the follow-up to OLM v0, located [here](https://github.com/operator-fr OLM v1 consists of four different components: * operator-controller (this repository) * [deppy](https://github.com/operator-framework/deppy) -* [rukpak](https://github.com/operator-framework/rukpak) * [catalogd](https://github.com/operator-framework/catalogd) For a more complete overview of OLM v1 and how it differs from OLM v0, see our [overview](./docs/olmv1_overview.md). @@ -76,7 +75,7 @@ Install the CRDs and the operator-controller into a new [KIND cluster](https://k make run ``` This will build a local container image of the operator-controller, create a new KIND cluster and then deploy onto that cluster. -This will also deploy the catalogd, rukpak and cert-manager dependencies. +This will also deploy the catalogd and cert-manager dependencies. ### Modifying the API definitions If you are editing the API definitions, generate the manifests such as CRs or CRDs using: diff --git a/Tiltfile b/Tiltfile index 948d0991d..783123d74 100644 --- a/Tiltfile +++ b/Tiltfile @@ -5,7 +5,7 @@ load('../tilt-support/Tiltfile', 'deploy_repo') config.define_string_list('repos', args=True) cfg = config.parse() -repos = cfg.get('repos', ['operator-controller', 'rukpak', 'catalogd']) +repos = cfg.get('repos', ['operator-controller', 'catalogd']) repo = { 'image': 'quay.io/operator-framework/operator-controller', diff --git a/api/v1alpha1/clusterextension_types.go b/api/v1alpha1/clusterextension_types.go index 11d7e7b13..c69803614 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 ( @@ -77,24 +82,31 @@ type ClusterExtensionSpec struct { const ( // TODO(user): add more Types, here and into init() - TypeInstalled = "Installed" - TypeResolved = "Resolved" + TypeInstalled = "Installed" + TypeResolved = "Resolved" + TypeHasValidBundle = "HasValidBundle" + // TypeDeprecated is a rollup condition that is present when // any of the deprecated conditions are present. TypeDeprecated = "Deprecated" TypePackageDeprecated = "PackageDeprecated" TypeChannelDeprecated = "ChannelDeprecated" TypeBundleDeprecated = "BundleDeprecated" + TypeUnpacked = "Unpacked" + + ReasonErrorGettingClient = "ErrorGettingClient" + ReasonBundleLoadFailed = "BundleLoadFailed" - ReasonBundleLookupFailed = "BundleLookupFailed" ReasonInstallationFailed = "InstallationFailed" ReasonInstallationStatusUnknown = "InstallationStatusUnknown" ReasonInstallationSucceeded = "InstallationSucceeded" - ReasonInvalidSpec = "InvalidSpec" ReasonResolutionFailed = "ResolutionFailed" - ReasonResolutionUnknown = "ResolutionUnknown" - ReasonSuccess = "Success" - ReasonDeprecated = "Deprecated" + + ReasonSuccess = "Success" + ReasonDeprecated = "Deprecated" + ReasonUpgradeFailed = "UpgradeFailed" + ReasonHasValidBundleUnknown = "HasValidBundleUnknown" + ReasonUnpackPending = "UnpackPending" ) func init() { @@ -102,22 +114,26 @@ func init() { conditionsets.ConditionTypes = append(conditionsets.ConditionTypes, TypeInstalled, TypeResolved, + TypeHasValidBundle, TypeDeprecated, TypePackageDeprecated, TypeChannelDeprecated, TypeBundleDeprecated, + TypeUnpacked, ) // TODO(user): add Reasons from above conditionsets.ConditionReasons = append(conditionsets.ConditionReasons, ReasonInstallationSucceeded, ReasonResolutionFailed, - ReasonResolutionUnknown, - ReasonBundleLookupFailed, ReasonInstallationFailed, - ReasonInstallationStatusUnknown, - ReasonInvalidSpec, ReasonSuccess, ReasonDeprecated, + ReasonUpgradeFailed, + ReasonBundleLoadFailed, + ReasonErrorGettingClient, + ReasonInstallationStatusUnknown, + ReasonHasValidBundleUnknown, + ReasonUnpackPending, ) } @@ -132,7 +148,6 @@ type ClusterExtensionStatus struct { InstalledBundle *BundleMetadata `json:"installedBundle,omitempty"` // +optional ResolvedBundle *BundleMetadata `json:"resolvedBundle,omitempty"` - // +patchMergeKey=type // +patchStrategy=merge // +listType=map diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 855d80c3e..09c7df940 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -17,39 +17,69 @@ limitations under the License. package main import ( + "crypto/x509" "flag" "fmt" "net/http" + "net/url" "os" "time" "github.com/spf13/pflag" "go.uber.org/zap/zapcore" + k8slabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" + crcache "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/server" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "github.com/operator-framework/rukpak/pkg/source" + "github.com/operator-framework/rukpak/pkg/storage" + + "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/handler" + "github.com/operator-framework/operator-controller/internal/labels" "github.com/operator-framework/operator-controller/internal/version" "github.com/operator-framework/operator-controller/pkg/features" "github.com/operator-framework/operator-controller/pkg/scheme" ) var ( - setupLog = ctrl.Log.WithName("setup") + setupLog = ctrl.Log.WithName("setup") + defaultUnpackImage = "quay.io/operator-framework/operator-controller:latest" + defaultSystemNamespace = "operator-controller-system" ) +// podNamespace checks whether the controller is running in a Pod vs. +// being run locally by inspecting the namespace file that gets mounted +// automatically for Pods at runtime. If that file doesn't exist, then +// return defaultSystemNamespace. +func podNamespace() string { + namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return defaultSystemNamespace + } + return string(namespace) +} + func main() { var ( - metricsAddr string - enableLeaderElection bool - probeAddr string - cachePath string - operatorControllerVersion bool + metricsAddr string + enableLeaderElection bool + probeAddr string + cachePath string + operatorControllerVersion bool + 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.") @@ -58,6 +88,9 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching") flag.BoolVar(&operatorControllerVersion, "version", false, "Prints operator-controller version information") + flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") + flag.StringVar(&unpackImage, "unpack-image", 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, } @@ -75,12 +108,33 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts), zap.StacktraceLevel(zapcore.DPanicLevel))) setupLog.Info("starting up the controller", "version info", version.String()) + if systemNamespace == "" { + systemNamespace = podNamespace() + } + + dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{v1alpha1.ClusterExtensionKind}) + if err != nil { + setupLog.Error(err, "unable to create dependent label selector for cache") + os.Exit(1) + } + dependentSelector := k8slabels.NewSelector().Add(*dependentRequirement) + + setupLog.Info("set up manager") mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme.Scheme, Metrics: server.Options{BindAddress: metricsAddr}, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "9c4404e7.operatorframework.io", + Cache: crcache.Options{ + ByObject: map[client.Object]crcache.ByObject{ + &v1alpha1.ClusterExtension{}: {}, + }, + DefaultNamespaces: map[string]crcache.Config{ + systemNamespace: {}, + crcache.AllNamespaces: {LabelSelector: dependentSelector}, + }, + }, // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly @@ -101,9 +155,39 @@ func main() { cl := mgr.GetClient() catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, &http.Client{Timeout: 10 * time.Second})) + cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), helmclient.StorageNamespaceMapper(func(o client.Object) (string, error) { + return systemNamespace, nil + })) + if err != nil { + setupLog.Error(err, "unable to config for creating helm client") + os.Exit(1) + } + + acg, err := helmclient.NewActionClientGetter(cfgGetter) + if err != nil { + setupLog.Error(err, "unable to create helm client") + os.Exit(1) + } + + unpacker, err := source.NewDefaultUnpacker(mgr, systemNamespace, unpackImage, (*x509.CertPool)(nil)) + if err != nil { + setupLog.Error(err, "unable to create unpacker") + os.Exit(1) + } + + localStorage := &storage.LocalDirectory{ + RootDirectory: provisionerStorageDirectory, + URL: url.URL{}, + } + if err = (&controllers.ClusterExtensionReconciler{ - Client: cl, - BundleProvider: catalogClient, + Client: cl, + ReleaseNamespace: systemNamespace, + BundleProvider: catalogClient, + ActionClientGetter: acg, + Unpacker: unpacker, + Storage: localStorage, + Handler: handler.HandlerFunc(handler.HandleClusterExtension), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") os.Exit(1) diff --git a/cmd/unpack/main.go b/cmd/unpack/main.go new file mode 100644 index 000000000..618bfc4ae --- /dev/null +++ b/cmd/unpack/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "io/fs" + "log" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/operator-framework/operator-controller/internal/version" +) + +func main() { + var bundleDir string + var operatorControllerVersion bool + + skipRootPaths := sets.NewString( + "/dev", + "/etc", + "/proc", + "/product_name", + "/product_uuid", + "/sys", + "/bin", + ) + cmd := &cobra.Command{ + Use: "unpack", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, _ []string) error { + if operatorControllerVersion { + log.Println(version.String()) + os.Exit(0) + } + var err error + bundleDir, err = filepath.Abs(bundleDir) + if err != nil { + log.Fatalf("get absolute path of bundle directory %q: %v", bundleDir, err) + } + + bundleFS := os.DirFS(bundleDir) + buf := &bytes.Buffer{} + gzw := gzip.NewWriter(buf) + tw := tar.NewWriter(gzw) + if err := fs.WalkDir(bundleFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.Type()&os.ModeSymlink != 0 { + return nil + } + if bundleDir == "/" { + // If bundleDir is the filesystem root, skip some known unrelated directories + fullPath := filepath.Join(bundleDir, path) + if skipRootPaths.Has(fullPath) { + return filepath.SkipDir + } + } + 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 := bundleFS.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 { + log.Fatalf("generate tar.gz for bundle dir %q: %v", bundleDir, err) + } + if err := tw.Close(); err != nil { + log.Fatal(err) + } + if err := gzw.Close(); err != nil { + log.Fatal(err) + } + + bundleMap := map[string]interface{}{ + "content": buf.Bytes(), + } + enc := json.NewEncoder(os.Stdout) + if err := enc.Encode(bundleMap); err != nil { + log.Fatalf("encode bundle map as JSON: %v", err) + } + return nil + }, + } + cmd.Flags().StringVar(&bundleDir, "bundle-dir", "", "directory in which the bundle can be found") + cmd.Flags().BoolVar(&operatorControllerVersion, "version", false, "displays operator-controller version information") + + if err := cmd.Execute(); err != nil { + log.Fatal(err) + } +} diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 754433c31..f888631fa 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -6,3 +6,23 @@ images: - name: controller newName: quay.io/operator-framework/operator-controller newTag: devel +replacements: +- source: # replaces UNPACK_IMAGE in manager.yaml with image set by kustomize above + kind: Deployment + group: apps + version: v1 + name: controller-manager + namespace: system + fieldPath: spec.template.spec.containers.[name=manager].image + targets: + - select: + kind: Deployment + group: apps + version: v1 + name: controller-manager + namespace: system + fieldPaths: + - spec.template.spec.containers.[name=manager].args.0 + options: + delimiter: "=" + index: 1 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 5c3b89f91..4f7c0eb91 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -52,6 +52,8 @@ spec: - command: - /manager args: + # The unpack-image arg must remain at index 0 for the kustomize replacement to work + - "--unpack-image=UNPACK_IMAGE" - "--health-probe-bind-address=:8081" - "--metrics-bind-address=127.0.0.1:8080" - "--leader-elect" @@ -61,6 +63,8 @@ spec: volumeMounts: - name: cache mountPath: /var/cache + - name: bundle-cache + mountPath: /var/cache/bundles securityContext: allowPrivilegeEscalation: false capabilities: @@ -111,3 +115,5 @@ spec: volumes: - name: cache emptyDir: {} + - name: bundle-cache + emptyDir: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 4652565c6..a0340fbef 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: - olm.operatorframework.io resources: diff --git a/docs/components.md b/docs/components.md index 13c97e939..395e9ca90 100644 --- a/docs/components.md +++ b/docs/components.md @@ -2,11 +2,6 @@ OLM v1 is composed of various component projects: * [operator-controller](https://github.com/operator-framework/operator-controller): operator-controller is the central component of OLM v1, that consumes all of the components below to extend Kubernetes to allows users to install, and manage the lifecycle of other extensions -* [rukpak](https://github.com/operator-framework/rukpak): RukPak is a pluggable solution for the packaging and distribution of cloud-native content and supports advanced strategies for installation, updates, and policy. The project provides a content ecosystem for installing a variety of artifacts, such as Git repositories, Helm charts, OLM bundles, and more onto a Kubernetes cluster. These artifacts can then be managed, scaled, and upgraded in a safe way to enable powerful cluster extensions. -At its core, RukPak is a small set of APIs, packaged as Kubernetes CustomResourceDefinitions, and controllers that watch for those APIs. These APIs express what content is being installed on-cluster and how to create a running deployment of the content. - - * [deppy](https://github.com/operator-framework/deppy): Deppy is a Kubernetes API that runs on- or off-cluster for resolving constraints over catalogs of RukPak bundles. Deppy is part of the next iteration of OLM and was first introduced here. The initial goal of the project is to remove the dependency manager from the Operator Lifecycle Manager (OLM) and make it its own generic component. - * [catalogD](https://github.com/operator-framework/catalogd): Catalogd is a Kubernetes extension that unpacks [file-based catalog (FBC)](https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs) content that is packaged and shipped in container images, for consumption by clients on-clusters (unpacking from other sources, like git repos, OCI artifacts etc, are in the roadmap for catalogD). As component of the Operator Lifecycle Manager (OLM) v1 microservices architecture, catalogD hosts metadata for Kubernetes extensions packaged by the authors of the extensions, as a result helping customers discover installable content. diff --git a/go.mod b/go.mod index fdfcda8f3..c740e962d 100644 --- a/go.mod +++ b/go.mod @@ -9,63 +9,185 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 + github.com/operator-framework/api v0.23.0 github.com/operator-framework/catalogd v0.12.0 + github.com/operator-framework/helm-operator-plugins v0.2.1 github.com/operator-framework/operator-registry v1.40.0 - github.com/operator-framework/rukpak v0.20.0 + github.com/operator-framework/rukpak v0.20.1-0.20240510194410-99faf1c1763f + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 gopkg.in/yaml.v2 v2.4.0 + helm.sh/helm/v3 v3.14.4 k8s.io/api v0.29.3 k8s.io/apimachinery v0.29.3 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.3 + sigs.k8s.io/yaml v1.4.0 ) require ( + dario.cat/mergo v1.0.0 // indirect + 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.3 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 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/cloudflare/circl v1.3.7 // indirect + github.com/containerd/cgroups/v3 v3.0.2 // indirect + github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/ttrpc v1.2.3 // indirect + github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/containers/common v0.58.2 // indirect + github.com/containers/image/v5 v5.30.0 // indirect + github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect + github.com/containers/ocicrypt v1.1.9 // indirect + github.com/containers/storage v1.53.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 v26.0.1+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker v25.0.5+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.1 // 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/emirpasic/gods v1.18.1 // 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-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.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.10 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/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.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.17.7 // 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.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // 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/sys/user v0.1.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/operator-framework/api v0.23.0 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/nlepage/go-tarfs v1.2.1 // indirect + github.com/onsi/gomega v1.33.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.2.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/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.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.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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/stretchr/objx v0.5.2 // 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/xanzy/ssh-agent v0.3.3 // 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.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 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + 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.17.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.7.0 // indirect @@ -73,16 +195,27 @@ require ( 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.20.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect + google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect 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/cli-runtime 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.3 // 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 d1c0c583f..adf916945 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,175 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.12.0-rc.3 h1:5GNGrobGs/sN/0nFO21W9k4lFn+iXXZAE8fCZbmdRak= +github.com/Microsoft/hcsshim v0.12.0-rc.3/go.mod h1:WuNfcaYNaw+KpCEsZCIM6HCEmu0c5HfXpi+dDSmveP0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/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-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 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= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/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/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= +github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= +github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= +github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= +github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= +github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containers/common v0.58.2 h1:5nu9lQz4QNSgovNk7NRk33SkqkVNKYoXh7L6gXmACow= +github.com/containers/common v0.58.2/go.mod h1:l3vMqanJGj7tZ3W/i76gEJ128VXgFUO1tLaohJXPvdk= +github.com/containers/image/v5 v5.30.0 h1:CmHeSwI6W2kTRWnUsxATDFY5TEX4b58gPkaQcEyrLIA= +github.com/containers/image/v5 v5.30.0/go.mod h1:gSD8MVOyqBspc0ynLsuiMR9qmt8UQ4jpVImjmK0uXfk= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/ocicrypt v1.1.9 h1:2Csfba4jse85Raxk5HIyEk8OwZNjRvfkhEGijOjIdEM= +github.com/containers/ocicrypt v1.1.9/go.mod h1:dTKx1918d8TDkxXvarscpNVY+lyPakPNFN4jwA9GBys= +github.com/containers/storage v1.53.0 h1:VSES3C/u1pxjTJIXvLrSmyP7OBtDky04oGu07UvdTEA= +github.com/containers/storage v1.53.0/go.mod h1:pujcoOSc+upx15Jirdkebhtd8uJiLwbSd/mYT6zDJK8= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +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.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= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiUFud7aeJCIQcgzugtwjyJo= +github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v26.0.1+incompatible h1:eZDuplk2jYqgUkNLDYwTBxqmY9cM3yHnmN6OIUEjL3U= +github.com/docker/cli v26.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= +github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= +github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 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.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.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/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.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= @@ -37,21 +178,59 @@ 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.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= +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/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/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= +github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree 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= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -61,135 +240,437 @@ 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-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +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= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/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= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/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.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joelanford/ignore v0.1.0 h1:VawbTDeg5EL+PN7W8gxVzGerfGpVo3gFdR5ZAqnkYRk= github.com/joelanford/ignore v0.1.0/go.mod h1:Vb0PQMAQXK29fmiPjDukpO8I2NTcp1y8LbhFijD1/0o= github.com/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 v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/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.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= +github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +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-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/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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/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/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/operator-framework/api v0.23.0 h1:kHymOwcHBpBVujT49SKOCd4EVG7Odwj4wl3NbOR2LLA= github.com/operator-framework/api v0.23.0/go.mod h1:oKcFOz+Xc1UhMi2Pzcp6qsO7wjS4r+yP7EQprQBXrfM= github.com/operator-framework/catalogd v0.12.0 h1:Cww+CyowkfTFugB9ZjUDpKvumh2vPe/TjCUpMHDmVBM= github.com/operator-framework/catalogd v0.12.0/go.mod h1:4lryGtBTVOdqlKR0MaVYnlsSOc7HiagVRVo3J4uIo7E= +github.com/operator-framework/helm-operator-plugins v0.2.1 h1:xPD05rYpIYKlI9VXfQBQ3XG7LFF2fALqg72lqAyFF44= +github.com/operator-framework/helm-operator-plugins v0.2.1/go.mod h1:SJyyAVYkl3tqiTJl16mz0KbGv89NhPOOGRBeQb8S4Nw= +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.40.0 h1:CaYNE4F/jzahpC7UCILItaIHmB5/oE0sS066nK+5Glw= github.com/operator-framework/operator-registry v1.40.0/go.mod h1:D2YxapkfRDgjqNTO9d3h3v0DeREbV+8utCLG52zrOy4= -github.com/operator-framework/rukpak v0.20.0 h1:BqF1nIlocyYLMmv6CvlbtB9QTwSMrEfTzhA+H3+do3c= -github.com/operator-framework/rukpak v0.20.0/go.mod h1:WAyS3DXZ19pLg/324PEoudWZmaRlYZ6i4j4NV3/T/mI= +github.com/operator-framework/rukpak v0.20.1-0.20240510194410-99faf1c1763f h1:9oJCWuUJ928v26u6Ny9pM6kLYuzfUT+iqkncbnFNNnQ= +github.com/operator-framework/rukpak v0.20.1-0.20240510194410-99faf1c1763f/go.mod h1:WAyS3DXZ19pLg/324PEoudWZmaRlYZ6i4j4NV3/T/mI= +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/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.8.0/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/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.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.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= +github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/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.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.7.0/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/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +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= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/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= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= +github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/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/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= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= +go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.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-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-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= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-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-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.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= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -198,30 +679,65 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +helm.sh/helm/v3 v3.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= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= @@ -230,6 +746,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.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= +k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= 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= @@ -238,12 +756,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.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= +k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= 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.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= 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/catalogmetadata/filter/bundle_predicates.go b/internal/catalogmetadata/filter/bundle_predicates.go index deca786b6..4dd7559fe 100644 --- a/internal/catalogmetadata/filter/bundle_predicates.go +++ b/internal/catalogmetadata/filter/bundle_predicates.go @@ -60,6 +60,12 @@ func WithBundleImage(bundleImage string) Predicate[catalogmetadata.Bundle] { } } +func WithBundleName(bundleName string) Predicate[catalogmetadata.Bundle] { + return func(bundle *catalogmetadata.Bundle) bool { + return bundle.Name == bundleName + } +} + func LegacySuccessor(installedBundle *catalogmetadata.Bundle) Predicate[catalogmetadata.Bundle] { isSuccessor := func(candidateBundleEntry declcfg.ChannelEntry) bool { if candidateBundleEntry.Replaces == installedBundle.Name { diff --git a/internal/catalogmetadata/filter/bundle_predicates_test.go b/internal/catalogmetadata/filter/bundle_predicates_test.go index fadbdfaf4..6392a0154 100644 --- a/internal/catalogmetadata/filter/bundle_predicates_test.go +++ b/internal/catalogmetadata/filter/bundle_predicates_test.go @@ -201,3 +201,16 @@ func TestWithDeprecation(t *testing.T) { assert.True(t, f(b1)) assert.False(t, f(b2)) } + +func TestWithBundleName(t *testing.T) { + b1 := &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{Name: "package1.v0.0.1"}, + } + b2 := &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{Name: "package1.v0.0.2"}, + } + + f := filter.WithBundleName("package1.v0.0.1") + assert.True(t, f(b1)) + assert.False(t, f(b2)) +} diff --git a/internal/catalogmetadata/types.go b/internal/catalogmetadata/types.go index b1d326ee6..1cf662ef9 100644 --- a/internal/catalogmetadata/types.go +++ b/internal/catalogmetadata/types.go @@ -89,12 +89,12 @@ func (b *Bundle) loadRequiredPackages() error { return fmt.Errorf("error determining bundle required packages for bundle %q: %s", b.Name, err) } for i := range requiredPackages { - semverRange, err := bsemver.ParseRange(requiredPackages[i].VersionRange) + semverRange, err := bsemver.ParseRange(requiredPackages[i].PackageRequired.VersionRange) if err != nil { return fmt.Errorf( "error parsing bundle required package semver range for bundle %q (required package %q): %s", - b.Name, - requiredPackages[i].PackageName, + b.Bundle.Name, + requiredPackages[i].PackageRequired.PackageName, err, ) } @@ -108,8 +108,8 @@ func (b *Bundle) loadRequiredPackages() error { func (b *Bundle) propertiesByType(propType string) []*property.Property { if b.propertiesMap == nil { b.propertiesMap = make(map[string][]*property.Property) - for i := range b.Properties { - prop := b.Properties[i] + for i := range b.Bundle.Properties { + prop := b.Bundle.Properties[i] b.propertiesMap[prop.Type] = append(b.propertiesMap[prop.Type], &prop) } } @@ -138,7 +138,7 @@ func (b *Bundle) HasDeprecation() bool { // bundle deprecation, so package deprecation is not considered. func (b *Bundle) IsDeprecated() bool { for _, dep := range b.Deprecations { - if dep.Reference.Schema == declcfg.SchemaBundle && dep.Reference.Name == b.Name { + if dep.Reference.Schema == declcfg.SchemaBundle && dep.Reference.Name == b.Bundle.Name { return true } } diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index d0d663b25..f92430005 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -17,78 +17,120 @@ limitations under the License. package controllers import ( + "bytes" "context" + "errors" "fmt" + "io" "sort" "strings" + "sync" + "time" 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" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/ptr" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" 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/client/apiutil" + crcontroller "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" + crhandler "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + "github.com/operator-framework/api/pkg/operators/v1alpha1" catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" + helmpredicate "github.com/operator-framework/rukpak/pkg/helm-operator-plugins/predicate" + rukpaksource "github.com/operator-framework/rukpak/pkg/source" + "github.com/operator-framework/rukpak/pkg/storage" + "github.com/operator-framework/rukpak/pkg/util" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" + "github.com/operator-framework/operator-controller/internal/conditionsets" + "github.com/operator-framework/operator-controller/internal/handler" + "github.com/operator-framework/operator-controller/internal/labels" ) // ClusterExtensionReconciler reconciles a ClusterExtension object type ClusterExtensionReconciler struct { client.Client - BundleProvider BundleProvider + ReleaseNamespace string + BundleProvider BundleProvider + Unpacker rukpaksource.Unpacker + ActionClientGetter helmclient.ActionClientGetter + Storage storage.Storage + Handler handler.Handler + 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 +// The operator controller needs to watch all the bundle objects and reconcile accordingly. Though not ideal, but these permissions are required. +// This has been taken from rukpak, and an issue was created before to discuss it: https://github.com/operator-framework/rukpak/issues/800. func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx).WithName("operator-controller") l.V(1).Info("starting") defer l.V(1).Info("ending") var existingExt = &ocv1alpha1.ClusterExtension{} - if err := r.Get(ctx, req.NamespacedName, existingExt); err != nil { - return ctrl.Result{}, client.IgnoreNotFound(err) + if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil { + return ctrl.Result{}, utilerrors.NewAggregate([]error{client.IgnoreNotFound(err), nil}) } reconciledExt := existingExt.DeepCopy() res, reconcileErr := r.reconcile(ctx, reconciledExt) + var updateErrors []error + // Do checks before any Update()s, as Update() may modify the resource structure! updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers) unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt) if updateStatus { - if updateErr := r.Status().Update(ctx, reconciledExt); updateErr != nil { - return res, utilerrors.NewAggregate([]error{reconcileErr, updateErr}) + if updateErr := r.Client.Status().Update(ctx, reconciledExt); updateErr != nil { + updateErrors = append(updateErrors, updateErr) } } @@ -97,12 +139,36 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req } if updateFinalizers { - if updateErr := r.Update(ctx, reconciledExt); updateErr != nil { - return res, utilerrors.NewAggregate([]error{reconcileErr, updateErr}) + if updateErr := r.Client.Update(ctx, reconciledExt); updateErr != nil { + updateErrors = append(updateErrors, updateErr) } } - return res, reconcileErr + if reconcileErr != nil { + updateErrors = append(updateErrors, reconcileErr) + } + + return res, utilerrors.NewAggregate(updateErrors) +} + +// ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension, +// and assigns a specified reason and custom message to any missing condition. +func ensureAllConditionsWithReason(ext *ocv1alpha1.ClusterExtension, reason v1alpha1.ConditionReason, message string) { + for _, condType := range conditionsets.ConditionTypes { + cond := apimeta.FindStatusCondition(ext.Status.Conditions, condType) + if cond == nil { + // Create a new condition with a valid reason and add it + newCond := metav1.Condition{ + Type: condType, + Status: metav1.ConditionFalse, + Reason: string(reason), + Message: message, + ObservedGeneration: ext.GetGeneration(), + LastTransitionTime: metav1.NewTime(time.Now()), + } + ext.Status.Conditions = append(ext.Status.Conditions, newCond) + } + } } // Compare resources - ignoring status & metadata.finalizers @@ -112,88 +178,249 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool { return !equality.Semantic.DeepEqual(a, b) } +func (r *ClusterExtensionReconciler) handleResolutionErrors(ext *ocv1alpha1.ClusterExtension, err error) (ctrl.Result, error) { + var aggErrs utilerrors.Aggregate + if errors.As(err, &aggErrs) { + for _, err := range aggErrs.Errors() { + errorMessage := err.Error() + if strings.Contains(errorMessage, "no package") { + // Handle no package found errors, potentially setting status conditions + setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation) + ensureAllConditionsWithReason(ext, "ResolutionFailed", errorMessage) + } else if strings.Contains(errorMessage, "invalid version range") { + // Handle invalid version range errors, potentially setting status conditions + setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation) + ensureAllConditionsWithReason(ext, "ResolutionFailed", errorMessage) + } else { + // General error handling + setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation) + ensureAllConditionsWithReason(ext, "InstallationStatusUnknown", "") + } + } + } else { + // If the error is not an aggregate, handle it as a general error + errorMessage := err.Error() + setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation) + ensureAllConditionsWithReason(ext, "InstallationStatusUnknown", "") + } + ext.Status.ResolvedBundle = nil + return ctrl.Result{}, err +} + // Helper function to do the actual reconcile // // Today we always return ctrl.Result{} and an error. // But in the future we might update this function // to return different results (e.g. requeue). // +/* The reconcile functions performs the following major tasks: +1. Resolution: Run the resolution to find the bundle from the catalog which needs to be installed. +2. Validate: Ensure that the bundle returned from the resolution for install meets our requirements. +3. Unpack: Unpack the contents from the bundle and store in a localdir in the pod. +4. Install: The process of installing involves: +4.1 Converting the CSV in the bundle into a set of plain k8s objects. +4.2 Generating a chart from k8s objects. +4.3 Apply the release on cluster. +*/ //nolint:unparam func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) { - // Lookup the bundle that corresponds to the ClusterExtension's desired package. - bundle, err := r.resolve(ctx, ext) + // run resolution + bundle, err := r.resolve(ctx, *ext) + if err != nil { + return r.handleResolutionErrors(ext, err) + } + + if err := r.validateBundle(bundle); 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 + } + // set deprecation status after _successful_ resolution + SetDeprecationStatus(ext, bundle) + + 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 + ext.Status.InstalledBundle = nil setResolvedStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration()) - - setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as resolution failed", ext.GetGeneration()) + setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.Generation) return ctrl.Result{}, err } - // 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? + // Generate a BundleDeployment from the ClusterExtension to Unpack. + // Note: The BundleDeployment here is not a k8s API, its a simple Go struct which + // necessary embedded values. + bd := r.generateBundleDeploymentForUnpack(bundle.Image, ext) + unpackResult, err := r.Unpacker.Unpack(ctx, bd) + if err != nil { + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, err) + } + + switch unpackResult.State { + case rukpaksource.StatePending: + updateStatusUnpackPending(&ext.Status, unpackResult, ext.GetGeneration()) + // There must be a limit to number of entries if status is stuck at + // unpack pending. + setHasValidBundleUnknown(&ext.Status.Conditions, "unpack pending", ext.GetGeneration()) + setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted as unpack is pending", ext.GetGeneration()) + + return ctrl.Result{}, nil + case rukpaksource.StateUnpacking: + updateStatusUnpacking(&ext.Status, unpackResult) + setHasValidBundleUnknown(&ext.Status.Conditions, "unpack pending", ext.GetGeneration()) + setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted as unpack is pending", ext.GetGeneration()) + return ctrl.Result{}, nil + case rukpaksource.StateUnpacked: + // TODO: Add finalizer to clean the stored bundles, after https://github.com/operator-framework/rukpak/pull/897 + // merges. + if err := r.Storage.Store(ctx, ext, unpackResult.Bundle); err != nil { + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, err) + } + updateStatusUnpacked(&ext.Status, unpackResult) + default: + return ctrl.Result{}, updateStatusUnpackFailing(&ext.Status, err) + } - if err := r.validateBundle(bundle); 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()) + bundleFS, err := r.Storage.Load(ctx, ext) + if err != nil { + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionFalse, + Reason: ocv1alpha1.ReasonBundleLoadFailed, + Message: err.Error(), + }) return ctrl.Result{}, err } - // Assume all bundles are registry+v1 for now, since that's all we support. - // If the bundle is not a registry+v1 bundle, the conversion will fail. - bundleProvisioner := "core-rukpak-io-registry" + chrt, values, err := r.Handler.Handle(ctx, bundleFS, ext) + if err != nil { + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ + Type: ocv1alpha1.TypeInstalled, + Status: metav1.ConditionFalse, + Reason: ocv1alpha1.ReasonInstallationFailed, + 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 + ac, err := r.ActionClientGetter.ActionClientFor(ctx, ext) + if err != nil { 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()) + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonErrorGettingClient, err), ext.Generation) 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()) + post := &postrenderer{ + labels: map[string]string{ + labels.OwnerKindKey: ocv1alpha1.ClusterExtensionKind, + labels.OwnerNameKey: ext.GetName(), + labels.BundleNameKey: bundle.Name, + labels.PackageNameKey: bundle.Package, + labels.BundleVersionKey: bundleVersion.String(), + }, + } + + rel, state, err := r.getReleaseState(ac, ext, chrt, values, post) + if err != nil { + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", rukpakv1alpha2.ReasonErrorGettingReleaseState, 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) + 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{labels.BundleNameKey: bundle.Name, labels.PackageNameKey: bundle.Package, labels.BundleVersionKey: bundleVersion.String()} + return nil + }, helmclient.AppendInstallPostRenderer(post)) + if err != nil { + if isResourceNotFoundErr(err) { + err = errRequiredResourceNotFound{err} + } + setInstalledStatusConditionFailed(&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} + } + setInstalledStatusConditionFailed(&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} + } + setInstalledStatusConditionFailed(&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) + } - SetDeprecationStatus(ext, bundle) + relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) + if err != nil { + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", rukpakv1alpha2.ReasonCreateDynamicWatchFailed, err), ext.Generation) + return ctrl.Result{}, err + } + + for _, obj := range relObjects { + uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", rukpakv1alpha2.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 { + ext.Status.InstalledBundle = nil + setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", rukpakv1alpha2.ReasonCreateDynamicWatchFailed, err), ext.Generation) + return ctrl.Result{}, err + } + } + ext.Status.InstalledBundle = bundleMetadataFor(bundle) + 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 } -func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { +// resolve returns a Bundle from the catalog that needs to get installed on the cluster. +func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { allBundles, err := r.BundleProvider.Bundles(ctx) if err != nil { - return nil, err - } - - installedBundle, err := r.installedBundle(ctx, allBundles, ext) - if err != nil { - return nil, err + return nil, utilerrors.NewAggregate([]error{fmt.Errorf("error fetching bundles: %w", err)}) } packageName := ext.Spec.PackageName channelName := ext.Spec.Channel versionRange := ext.Spec.Version + installedBundle, err := GetInstalledbundle(ctx, r.ActionClientGetter, allBundles, &ext) + if err != nil { + return nil, err + } + predicates := []catalogfilter.Predicate[catalogmetadata.Bundle]{ catalogfilter.WithPackageName(packageName), } @@ -205,7 +432,7 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext *ocv1alpha if versionRange != "" { vr, err := mmsemver.NewConstraint(versionRange) if err != nil { - return nil, fmt.Errorf("invalid version range %q: %w", versionRange, err) + return nil, utilerrors.NewAggregate([]error{fmt.Errorf("invalid version range '%s': %w", versionRange, err)}) } predicates = append(predicates, catalogfilter.InMastermindsSemverRange(vr)) } @@ -230,17 +457,18 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext *ocv1alpha upgradeErrorPrefix = fmt.Sprintf("error upgrading from currently installed version %q: ", installedBundleVersion.String()) } if len(resultSet) == 0 { - if versionRange != "" && channelName != "" { - return nil, fmt.Errorf("%sno package %q matching version %q found in channel %q", upgradeErrorPrefix, packageName, versionRange, channelName) - } - if versionRange != "" { + switch { + case versionRange != "" && channelName != "": + return nil, fmt.Errorf("%sno package %q matching version %q in channel %q found", upgradeErrorPrefix, packageName, versionRange, channelName) + case versionRange != "": return nil, fmt.Errorf("%sno package %q matching version %q found", upgradeErrorPrefix, packageName, versionRange) + case channelName != "": + return nil, fmt.Errorf("%sno package %q in channel %q found", upgradeErrorPrefix, packageName, channelName) + default: + return nil, fmt.Errorf("%sno package %q found", upgradeErrorPrefix, packageName) } - if channelName != "" { - return nil, fmt.Errorf("%sno package %q found in channel %q", upgradeErrorPrefix, packageName, channelName) - } - return nil, fmt.Errorf("%sno package %q found", upgradeErrorPrefix, packageName) } + sort.SliceStable(resultSet, func(i, j int) bool { return catalogsort.ByVersion(resultSet[i], resultSet[j]) }) @@ -251,100 +479,7 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext *ocv1alpha return resultSet[0], nil } -func (r *ClusterExtensionReconciler) installedBundle(ctx context.Context, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { - bd := &rukpakv1alpha2.BundleDeployment{} - err := r.Client.Get(ctx, types.NamespacedName{Name: ext.GetName()}, bd) - if client.IgnoreNotFound(err) != nil { - return nil, err - } - - if bd.Spec.Source.Image == nil || bd.Spec.Source.Image.Ref == "" { - // Bundle not yet installed - return nil, nil - } - - bundleImage := bd.Spec.Source.Image.Ref - // find corresponding bundle for the installed content - resultSet := catalogfilter.Filter(allBundles, catalogfilter.And( - catalogfilter.WithPackageName(ext.Spec.PackageName), - catalogfilter.WithBundleImage(bundleImage), - )) - if len(resultSet) == 0 { - return nil, fmt.Errorf("bundle with image %q for package %q not found in available catalogs but is currently installed via BundleDeployment %q", bundleImage, ext.Spec.PackageName, bd.Name) - } - - sort.SliceStable(resultSet, func(i, j int) bool { - return catalogsort.ByVersion(resultSet[i], resultSet[j]) - }) - - return resultSet[0], nil -} - -func (r *ClusterExtensionReconciler) validateBundle(bundle *catalogmetadata.Bundle) error { - unsupportedProps := sets.New( - property.TypePackageRequired, - property.TypeGVKRequired, - property.TypeConstraint, - ) - for i := range bundle.Properties { - if unsupportedProps.Has(bundle.Properties[i].Type) { - return fmt.Errorf( - "bundle %q has a dependency declared via property %q which is currently not supported", - bundle.Name, - bundle.Properties[i].Type, - ) - } - } - - return nil -} - -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 - } - - if bundleDeploymentReady.Status != metav1.ConditionTrue { - ext.Status.InstalledBundle = nil - setInstalledStatusConditionFailed( - &ext.Status.Conditions, - fmt.Sprintf("bundledeployment not ready: %s", bundleDeploymentReady.Message), - ext.GetGeneration(), - ) - return - } - - 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(), - ) - } -} - -// setDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension +// SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension // based on the provided bundle func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundle *catalogmetadata.Bundle) { // reset conditions to false @@ -426,52 +561,33 @@ func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundle *catalogmetad } } -func (r *ClusterExtensionReconciler) GenerateExpectedBundleDeployment(o ocv1alpha1.ClusterExtension, bundlePath string, bundleProvisioner string) *unstructured.Unstructured { - // We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver. - // If you use a typed object, any default values from that struct get serialized into the JSON patch, which could - // cause unrelated fields to be patched back to the default value even though that isn't the intention. Using an - // unstructured ensures that the patch contains only what is specified. Using unstructured like this is basically - // identical to "kubectl apply -f" - - spec := map[string]interface{}{ - "installNamespace": o.Spec.InstallNamespace, - "provisionerClassName": bundleProvisioner, - "source": map[string]interface{}{ - // TODO: Don't assume image type - "type": string(rukpakv1alpha2.SourceTypeImage), - "image": map[string]interface{}{ - "ref": bundlePath, - }, +func (r *ClusterExtensionReconciler) generateBundleDeploymentForUnpack(bundlePath string, ce *ocv1alpha1.ClusterExtension) *rukpakv1alpha2.BundleDeployment { + return &rukpakv1alpha2.BundleDeployment{ + TypeMeta: metav1.TypeMeta{ + Kind: ce.Kind, + APIVersion: ce.APIVersion, }, - } - - bd := &unstructured.Unstructured{Object: map[string]interface{}{ - "apiVersion": rukpakv1alpha2.GroupVersion.String(), - "kind": rukpakv1alpha2.BundleDeploymentKind, - "metadata": map[string]interface{}{ - "name": o.GetName(), + ObjectMeta: metav1.ObjectMeta{ + Name: ce.Name, + UID: ce.UID, }, - "spec": spec, - }} - bd.SetOwnerReferences([]metav1.OwnerReference{ - { - APIVersion: ocv1alpha1.GroupVersion.String(), - Kind: "ClusterExtension", - Name: o.Name, - UID: o.UID, - Controller: ptr.To(true), - BlockOwnerDeletion: ptr.To(true), + Spec: rukpakv1alpha2.BundleDeploymentSpec{ + Source: rukpakv1alpha2.BundleSource{ + Type: rukpakv1alpha2.SourceTypeImage, + Image: &rukpakv1alpha2.ImageSource{ + Ref: bundlePath, + }, + }, }, - }) - return bd + } } // 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()))). + crhandler.EnqueueRequestsFromMapFunc(clusterExtensionRequestsForCatalog(mgr.GetClient(), mgr.GetLogger()))). WithEventFilter(predicate.Funcs{ UpdateFunc: func(ue event.UpdateEvent) bool { oldObject, isOldCatalog := ue.ObjectOld.(*catalogd.Catalog) @@ -489,53 +605,56 @@ func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error { return true }, }). - Owns(&rukpakv1alpha2.BundleDeployment{}). - Complete(r) + Watches(&corev1.Pod{}, mapOwneeToOwnerHandler(mgr.GetClient(), mgr.GetLogger(), &ocv1alpha1.ClusterExtension{})). + Build(r) if err != nil { return err } + r.controller = controller + r.cache = mgr.GetCache() + r.dynamicWatchGVKs = map[schema.GroupVersionKind]struct{}{} 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 + } - 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 + } + 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{} @@ -557,6 +676,137 @@ func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) han } } +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 +} + +var GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + cl, err := acg.ActionClientFor(ctx, ext) + if err != nil { + return nil, err + } + + release, err := cl.Get(ext.GetName()) + if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) { + return nil, err + } + if release == nil { + return nil, nil + } + + // Bundle must match installed version exactly + vr, err := mmsemver.NewConstraint(release.Labels[labels.BundleVersionKey]) + if err != nil { + return nil, err + } + + // find corresponding bundle for the installed content + resultSet := catalogfilter.Filter(allBundles, catalogfilter.And( + catalogfilter.WithPackageName(release.Labels[labels.PackageNameKey]), + catalogfilter.WithBundleName(release.Labels[labels.BundleNameKey]), + catalogfilter.InMastermindsSemverRange(vr), + )) + if len(resultSet) == 0 { + return nil, fmt.Errorf("bundle %q for package %q not found in available catalogs but is currently installed in namespace %q", release.Labels[labels.BundleNameKey], ext.Spec.PackageName, release.Namespace) + } + + sort.SliceStable(resultSet, func(i, j int) bool { + return catalogsort.ByVersion(resultSet[i], resultSet[j]) + }) + + return resultSet[0], 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, err + } + obj.SetLabels(util.MergeMaps(obj.GetLabels(), p.labels)) + b, err := obj.MarshalJSON() + if err != nil { + return nil, err + } + buf.Write(b) + } + if p.cascade != nil { + return p.cascade.Run(&buf) + } + return &buf, nil +} + // bundleMetadataFor returns a BundleMetadata for the given bundle. If the provided bundle is nil, // this function panics. It is up to the caller to ensure that the bundle is non-nil. func bundleMetadataFor(bundle *catalogmetadata.Bundle) *ocv1alpha1.BundleMetadata { @@ -572,3 +822,22 @@ func bundleMetadataFor(bundle *catalogmetadata.Bundle) *ocv1alpha1.BundleMetadat Version: ver.String(), } } + +func (r *ClusterExtensionReconciler) validateBundle(bundle *catalogmetadata.Bundle) error { + unsupportedProps := sets.New( + property.TypePackageRequired, + property.TypeGVKRequired, + property.TypeConstraint, + ) + for i := range bundle.Properties { + if unsupportedProps.Has(bundle.Properties[i].Type) { + return fmt.Errorf( + "bundle %q has a dependency declared via property %q which is currently not supported", + bundle.Name, + bundle.Properties[i].Type, + ) + } + } + + return nil +} diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go index f5f389514..08fd0da2b 100644 --- a/internal/controllers/clusterextension_controller_test.go +++ b/internal/controllers/clusterextension_controller_test.go @@ -9,20 +9,21 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" featuregatetesting "k8s.io/component-base/featuregate/testing" - "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" + "github.com/operator-framework/rukpak/pkg/source" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" @@ -81,7 +82,6 @@ func TestClusterExtensionNonExistentPackage(t *testing.T) { verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionNonExistentVersion(t *testing.T) { @@ -123,519 +123,20 @@ func TestClusterExtensionNonExistentVersion(t *testing.T) { require.Equal(t, fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName), cond.Message) cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) -} - -func TestClusterExtensionBundleDeploymentDoesNotExist(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - const pkgName = "prometheus" - installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - - t.Log("When the cluster extension specifies a valid available package") - t.Log("By initializing cluster state") - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - InstallNamespace: installNamespace, - }, - } - require.NoError(t, cl.Create(ctx, clusterExtension)) - - t.Log("When the BundleDeployment does not exist") - t.Log("By running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("It results in the expected BundleDeployment") - bd := &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: extKey.Name}, bd)) - require.Equal(t, "core-rukpak-io-registry", bd.Spec.ProvisionerClassName) - require.Equal(t, installNamespace, bd.Spec.InstallNamespace) - require.Equal(t, rukpakv1alpha2.SourceTypeImage, bd.Spec.Source.Type) - require.NotNil(t, bd.Spec.Source.Image) - require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Source.Image.Ref) - - t.Log("It sets the ResolvedBundle status field") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, clusterExtension.Status.ResolvedBundle) - - t.Log("It sets the InstalledBundle status field") - require.Empty(t, clusterExtension.Status.InstalledBundle) - - t.Log("It sets the status on the cluster extension") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - - cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) -} - -func TestClusterExtensionBundleDeploymentOutOfDate(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - const pkgName = "prometheus" - installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - - t.Log("When the cluster extension specifies a valid available package") - t.Log("By initializing cluster state") - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - InstallNamespace: installNamespace, - }, - } - require.NoError(t, cl.Create(ctx, clusterExtension)) - - t.Log("When the expected BundleDeployment already exists") - t.Log("When the BundleDeployment spec is out of date") - t.Log("By patching the existing BD") - bd := &rukpakv1alpha2.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: extKey.Name, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: ocv1alpha1.GroupVersion.String(), - Kind: "ClusterExtension", - Name: clusterExtension.Name, - UID: clusterExtension.UID, - Controller: ptr.To(true), - BlockOwnerDeletion: ptr.To(true), - }, - }, - }, - Spec: rukpakv1alpha2.BundleDeploymentSpec{ - InstallNamespace: "default", - ProvisionerClassName: "core-rukpak-io-registry", - Source: rukpakv1alpha2.BundleSource{ - Type: rukpakv1alpha2.SourceTypeImage, - Image: &rukpakv1alpha2.ImageSource{ - Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", - }, - }, - }, - } - - t.Log("By modifying the BD spec and creating the object") - bd.Spec.InstallNamespace = "incorrect" - bd.Spec.ProvisionerClassName = "core-rukpak-io-helm" - require.NoError(t, cl.Create(ctx, bd)) - - t.Log("It results in the expected BundleDeployment") - - t.Log("By running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("By checking the expected BD spec") - bd = &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: extKey.Name}, bd)) - require.Equal(t, "core-rukpak-io-registry", bd.Spec.ProvisionerClassName) - require.Equal(t, installNamespace, bd.Spec.InstallNamespace) - require.Equal(t, rukpakv1alpha2.SourceTypeImage, bd.Spec.Source.Type) - require.NotNil(t, bd.Spec.Source.Image) - require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Source.Image.Ref) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, clusterExtension.Status.ResolvedBundle) - require.Empty(t, clusterExtension.Status.InstalledBundle) - - t.Log("By checking the expected status conditions") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) -} - -func TestClusterExtensionBundleDeploymentUpToDate(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - const pkgName = "prometheus" - installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - - t.Log("When the cluster extension specifies a valid available package") - t.Log("By initializing cluster state") - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - InstallNamespace: installNamespace, - }, - } - require.NoError(t, cl.Create(ctx, clusterExtension)) - - t.Log("When the expected BundleDeployment already exists") - t.Log("When the BundleDeployment spec is up-to-date") - t.Log("By patching the existing BD") - bd := &rukpakv1alpha2.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: extKey.Name, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: ocv1alpha1.GroupVersion.String(), - Kind: "ClusterExtension", - Name: clusterExtension.Name, - UID: clusterExtension.UID, - Controller: ptr.To(true), - BlockOwnerDeletion: ptr.To(true), - }, - }, - }, - Spec: rukpakv1alpha2.BundleDeploymentSpec{ - InstallNamespace: installNamespace, - ProvisionerClassName: "core-rukpak-io-registry", - Source: rukpakv1alpha2.BundleSource{ - Type: rukpakv1alpha2.SourceTypeImage, - Image: &rukpakv1alpha2.ImageSource{ - Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", - }, - }, - }, - } - - require.NoError(t, cl.Create(ctx, bd)) - bd.Status.ObservedGeneration = bd.GetGeneration() - - t.Log("When the BundleDeployment status is mapped to the expected ClusterExtension status") - t.Log("It verifies cluster extension status when bundle deployment is waiting to be created") - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("By running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext := &ocv1alpha1.ClusterExtension{} - require.NoError(t, cl.Get(ctx, extKey, ext)) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Empty(t, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond := apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - t.Log("It verifies cluster extension status when `Unpacked` condition of rukpak is false") - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeUnpacked, - Status: metav1.ConditionFalse, - Message: "failed to unpack", - Reason: rukpakv1alpha2.ReasonUnpackFailed, - }) - - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("By running reconcile") - res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext = &ocv1alpha1.ClusterExtension{} - require.NoError(t, cl.Get(ctx, extKey, ext)) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Nil(t, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - t.Log("It verifies cluster extension status when `InstallReady` condition of rukpak is false") - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeInstalled, - Status: metav1.ConditionFalse, - Message: "failed to install", - Reason: rukpakv1alpha2.ReasonInstallFailed, - }) - - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("By running reconcile") - res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext = &ocv1alpha1.ClusterExtension{} - err = cl.Get(ctx, extKey, ext) - require.NoError(t, err) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Empty(t, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionFalse, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationFailed, cond.Reason) - require.Contains(t, cond.Message, `failed to install`) - - t.Log("It verifies cluster extension status when `InstallReady` condition of rukpak is true") - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeInstalled, - Status: metav1.ConditionTrue, - Message: "cluster extension installed successfully", - Reason: rukpakv1alpha2.ReasonInstallationSucceeded, - }) - - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("By running reconcile") - res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext = &ocv1alpha1.ClusterExtension{} - require.NoError(t, cl.Get(ctx, extKey, ext)) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - - t.Log("It verifies any other unknown status of bundledeployment") - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeUnpacked, - Status: metav1.ConditionUnknown, - Message: "unpacking", - Reason: rukpakv1alpha2.ReasonUnpackSuccessful, - }) - - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha2.ReasonInstallationSucceeded, - }) - - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("By running reconcile") - res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext = &ocv1alpha1.ClusterExtension{} - require.NoError(t, cl.Get(ctx, extKey, ext)) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Nil(t, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) require.Equal(t, metav1.ConditionFalse, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationFailed, cond.Reason) - require.Equal(t, "bundledeployment not ready: installing", cond.Message) - - t.Log("It verifies cluster extension status when bundleDeployment installation status is unknown") - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha2.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha2.ReasonInstallationSucceeded, - }) - - t.Log("By updating the status of bundleDeployment") - require.NoError(t, cl.Status().Update(ctx, bd)) - - t.Log("running reconcile") - res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching the updated cluster extension after reconcile") - ext = &ocv1alpha1.ClusterExtension{} - require.NoError(t, cl.Get(ctx, extKey, ext)) - - t.Log("By checking the status fields") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, ext.Status.ResolvedBundle) - require.Nil(t, ext.Status.InstalledBundle) - - t.Log("By checking the expected conditions") - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionFalse, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationFailed, cond.Reason) - require.Equal(t, "bundledeployment not ready: installing", cond.Message) verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) -} - -func TestClusterExtensionExpectedBundleDeployment(t *testing.T) { - cl, reconciler := newClientAndReconciler(t) - ctx := context.Background() - extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} - const pkgName = "prometheus" - installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) - - t.Log("When the cluster extension specifies a valid available package") - t.Log("By initializing cluster state") - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: pkgName, - InstallNamespace: installNamespace, - }, - } - require.NoError(t, cl.Create(ctx, clusterExtension)) - - t.Log("When an out-of-date BundleDeployment exists") - t.Log("By creating the expected BD") - bd := &rukpakv1alpha2.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{Name: extKey.Name}, - Spec: rukpakv1alpha2.BundleDeploymentSpec{ - InstallNamespace: "foo", - ProvisionerClassName: "bar", - Source: rukpakv1alpha2.BundleSource{ - Type: rukpakv1alpha2.SourceTypeHTTP, - HTTP: &rukpakv1alpha2.HTTPSource{ - URL: "http://localhost:8080/", - }, - }, - }, - } - require.NoError(t, cl.Create(ctx, bd)) - - t.Log("By running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) - require.Equal(t, ctrl.Result{}, res) - require.NoError(t, err) - - t.Log("By fetching updated cluster extension after reconcile") - require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) - - t.Log("It results in the expected BundleDeployment") - bd = &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: extKey.Name}, bd)) - require.Equal(t, "core-rukpak-io-registry", bd.Spec.ProvisionerClassName) - require.Equal(t, installNamespace, bd.Spec.InstallNamespace) - require.Equal(t, rukpakv1alpha2.SourceTypeImage, bd.Spec.Source.Type) - require.NotNil(t, bd.Spec.Source.Image) - require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Source.Image.Ref) - - t.Log("It sets the ResolvedBundle status field") - require.Equal(t, &ocv1alpha1.BundleMetadata{Name: "operatorhub/prometheus/beta/2.0.0", Version: "2.0.0"}, clusterExtension.Status.ResolvedBundle) - - t.Log("It sets the InstalledBundle status field") - require.Empty(t, clusterExtension.Status.InstalledBundle) - - t.Log("It sets resolution to unknown status") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) - require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionChannelVersionExists(t *testing.T) { cl, reconciler := newClientAndReconciler(t) + mockUnpacker := unpacker.(*MockUnpacker) + // Set up the Unpack method to return a result with StateUnpacked + mockUnpacker.On("Unpack", mock.Anything, mock.AnythingOfType("*v1alpha2.BundleDeployment")).Return(&source.Result{ + State: source.StatePending, + }, nil) + ctx := context.Background() extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} @@ -672,33 +173,29 @@ func TestClusterExtensionChannelVersionExists(t *testing.T) { require.Empty(t, clusterExtension.Status.InstalledBundle) t.Log("By checking the expected conditions") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) + resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) + require.NotNil(t, resolvedCond) + require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) + require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", resolvedCond.Message) + + t.Log("By checking the expected unpacked conditions") + unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, rukpakv1alpha2.TypeUnpacked) + require.NotNil(t, unpackedCond) + require.Equal(t, metav1.ConditionFalse, unpackedCond.Status) + require.Equal(t, rukpakv1alpha2.ReasonUnpackPending, unpackedCond.Reason) - t.Log("By fetching the bundled deployment") - bd := &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: extKey.Name}, bd)) - require.Equal(t, "core-rukpak-io-registry", bd.Spec.ProvisionerClassName) - require.Equal(t, installNamespace, bd.Spec.InstallNamespace) - require.Equal(t, rukpakv1alpha2.SourceTypeImage, bd.Spec.Source.Type) - require.NotNil(t, bd.Spec.Source.Image) - require.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", bd.Spec.Source.Image.Ref) - - verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionChannelExistsNoVersion(t *testing.T) { cl, reconciler := newClientAndReconciler(t) + mockUnpacker := unpacker.(*MockUnpacker) + // Set up the Unpack method to return a result with StateUnpacked + mockUnpacker.On("Unpack", mock.Anything, mock.AnythingOfType("*v1alpha2.BundleDeployment")).Return(&source.Result{ + State: source.StatePending, + }, nil) + ctx := context.Background() extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))} @@ -717,13 +214,15 @@ func TestClusterExtensionChannelExistsNoVersion(t *testing.T) { InstallNamespace: installNamespace, }, } - require.NoError(t, cl.Create(ctx, clusterExtension)) + err := cl.Create(ctx, clusterExtension) + require.NoError(t, err) t.Log("It sets resolution success status") t.Log("By running reconcile") res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Equal(t, ctrl.Result{}, res) require.NoError(t, err) + t.Log("By fetching updated cluster extension after reconcile") require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) @@ -732,29 +231,20 @@ func TestClusterExtensionChannelExistsNoVersion(t *testing.T) { require.Empty(t, clusterExtension.Status.InstalledBundle) t.Log("By checking the expected conditions") - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionTrue, cond.Status) - require.Equal(t, ocv1alpha1.ReasonSuccess, cond.Reason) - require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) - cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) - require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "bundledeployment status is unknown", cond.Message) - - t.Log("By fetching the bundledeployment") - bd := &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: extKey.Name}, bd)) - require.Equal(t, "core-rukpak-io-registry", bd.Spec.ProvisionerClassName) - require.Equal(t, installNamespace, bd.Spec.InstallNamespace) - require.Equal(t, rukpakv1alpha2.SourceTypeImage, bd.Spec.Source.Type) - require.NotNil(t, bd.Spec.Source.Image) - require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Source.Image.Ref) + resolvedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) + require.NotNil(t, resolvedCond) + require.Equal(t, metav1.ConditionTrue, resolvedCond.Status) + require.Equal(t, ocv1alpha1.ReasonSuccess, resolvedCond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", resolvedCond.Message) + + t.Log("By checking the expected unpacked conditions") + unpackedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, rukpakv1alpha2.TypeUnpacked) + require.NotNil(t, unpackedCond) + require.Equal(t, metav1.ConditionFalse, unpackedCond.Status) + require.Equal(t, rukpakv1alpha2.ReasonUnpackPending, unpackedCond.Reason) verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionVersionNoChannel(t *testing.T) { @@ -782,7 +272,7 @@ func TestClusterExtensionVersionNoChannel(t *testing.T) { t.Log("By running reconcile") res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Equal(t, ctrl.Result{}, res) - require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q in channel %q found", pkgName, pkgVer, pkgChan)) t.Log("By fetching updated cluster extension after reconcile") require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) @@ -796,17 +286,15 @@ func TestClusterExtensionVersionNoChannel(t *testing.T) { require.NotNil(t, cond) require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + require.Equal(t, fmt.Sprintf("no package %q matching version %q in channel %q found", pkgName, pkgVer, pkgChan), cond.Message) cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionNoChannel(t *testing.T) { @@ -832,7 +320,7 @@ func TestClusterExtensionNoChannel(t *testing.T) { t.Log("By running reconcile") res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Equal(t, ctrl.Result{}, res) - require.EqualError(t, err, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan)) + require.EqualError(t, err, fmt.Sprintf("no package %q in channel %q found", pkgName, pkgChan)) t.Log("By fetching updated cluster extension after reconcile") require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) @@ -846,16 +334,14 @@ func TestClusterExtensionNoChannel(t *testing.T) { require.NotNil(t, cond) require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - require.Equal(t, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan), cond.Message) + require.Equal(t, fmt.Sprintf("no package %q in channel %q found", pkgName, pkgChan), cond.Message) cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func TestClusterExtensionNoVersion(t *testing.T) { @@ -883,7 +369,7 @@ func TestClusterExtensionNoVersion(t *testing.T) { t.Log("By running reconcile") res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Equal(t, ctrl.Result{}, res) - require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q in channel %q found", pkgName, pkgVer, pkgChan)) t.Log("By fetching updated cluster extension after reconcile") require.NoError(t, cl.Get(ctx, extKey, clusterExtension)) @@ -897,16 +383,14 @@ func TestClusterExtensionNoVersion(t *testing.T) { require.NotNil(t, cond) require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + require.Equal(t, fmt.Sprintf("no package %q matching version %q in channel %q found", pkgName, pkgVer, pkgChan), cond.Message) cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) require.NotNil(t, cond) - require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, metav1.ConditionFalse, cond.Status) require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) - require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) verifyInvariants(ctx, t, reconciler.Client, clusterExtension) require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) } func verifyInvariants(ctx context.Context, t *testing.T, c client.Client, ext *ocv1alpha1.ClusterExtension) { @@ -931,51 +415,24 @@ func verifyConditionsInvariants(t *testing.T, ext *ocv1alpha1.ClusterExtension) } } -func TestGeneratedBundleDeployment(t *testing.T) { - test := []struct { - name string - clusterExtension ocv1alpha1.ClusterExtension - bundlePath string - bundleProvisioner string - }{ - { - name: "when all the specs are provided.", - clusterExtension: ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-bd", - UID: types.UID("test"), - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - InstallNamespace: "test-ns", - }, - }, - bundlePath: "testpath", - bundleProvisioner: "foo", - }, - } - - for _, tt := range test { - fakeReconciler := &controllers.ClusterExtensionReconciler{} - objUnstructured := fakeReconciler.GenerateExpectedBundleDeployment(tt.clusterExtension, tt.bundlePath, tt.bundleProvisioner) - resultBundleDeployment := &rukpakv1alpha2.BundleDeployment{} - require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(objUnstructured.Object, resultBundleDeployment)) - // Verify the fields that have are being taken from cluster extension. - require.Equal(t, tt.clusterExtension.GetName(), resultBundleDeployment.GetName()) - require.Equal(t, tt.bundlePath, resultBundleDeployment.Spec.Source.Image.Ref) - require.Equal(t, tt.bundleProvisioner, resultBundleDeployment.Spec.ProvisionerClassName) - require.Equal(t, tt.clusterExtension.Spec.InstallNamespace, resultBundleDeployment.Spec.InstallNamespace) - } -} - func TestClusterExtensionUpgrade(t *testing.T) { cl, reconciler := newClientAndReconciler(t) + mockUnpacker := unpacker.(*MockUnpacker) + // Set up the Unpack method to return a result with StateUnpackPending + mockUnpacker.On("Unpack", mock.Anything, mock.AnythingOfType("*v1alpha2.BundleDeployment")).Return(&source.Result{ + State: source.StatePending, + }, nil) ctx := context.Background() + defer func() { + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return nil, nil + } + }() t.Run("semver upgrade constraints enforcement of upgrades within major version", func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() pkgName := "prometheus" @@ -1020,6 +477,22 @@ func TestClusterExtensionUpgrade(t *testing.T) { err = cl.Update(ctx, clusterExtension) require.NoError(t, err) + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, nil + } + // Run reconcile again res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Error(t, err) @@ -1038,7 +511,7 @@ func TestClusterExtensionUpgrade(t *testing.T) { require.NotNil(t, cond) assert.Equal(t, metav1.ConditionFalse, cond.Status) assert.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Equal(t, "error upgrading from currently installed version \"1.0.0\": no package \"prometheus\" matching version \"2.0.0\" found in channel \"beta\"", cond.Message) + assert.Equal(t, "error upgrading from currently installed version \"1.0.0\": no package \"prometheus\" matching version \"2.0.0\" in channel \"beta\" found", cond.Message) // Valid update skipping one version clusterExtension.Spec.Version = "1.2.0" @@ -1069,7 +542,6 @@ func TestClusterExtensionUpgrade(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() pkgName := "prometheus" @@ -1114,6 +586,22 @@ func TestClusterExtensionUpgrade(t *testing.T) { err = cl.Update(ctx, clusterExtension) require.NoError(t, err) + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, nil + } + // Run reconcile again res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Error(t, err) @@ -1132,7 +620,7 @@ func TestClusterExtensionUpgrade(t *testing.T) { require.NotNil(t, cond) assert.Equal(t, metav1.ConditionFalse, cond.Status) assert.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Equal(t, "error upgrading from currently installed version \"1.0.0\": no package \"prometheus\" matching version \"1.2.0\" found in channel \"beta\"", cond.Message) + assert.Equal(t, "error upgrading from currently installed version \"1.0.0\": no package \"prometheus\" matching version \"1.2.0\" in channel \"beta\" found", cond.Message) // Valid update skipping one version clusterExtension.Spec.Version = "1.0.1" @@ -1177,7 +665,6 @@ func TestClusterExtensionUpgrade(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) @@ -1222,6 +709,22 @@ func TestClusterExtensionUpgrade(t *testing.T) { err = cl.Update(ctx, clusterExtension) require.NoError(t, err) + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, nil + } + // Run reconcile again res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.NoError(t, err) @@ -1247,7 +750,17 @@ func TestClusterExtensionUpgrade(t *testing.T) { func TestClusterExtensionDowngrade(t *testing.T) { cl, reconciler := newClientAndReconciler(t) + mockUnpacker := unpacker.(*MockUnpacker) + // Set up the Unpack method to return a result with StateUnpacked + mockUnpacker.On("Unpack", mock.Anything, mock.AnythingOfType("*v1alpha2.BundleDeployment")).Return(&source.Result{ + State: source.StatePending, + }, nil) ctx := context.Background() + defer func() { + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return nil, nil + } + }() t.Run("enforce upgrade constraints", func(t *testing.T) { for _, tt := range []struct { @@ -1267,7 +780,6 @@ func TestClusterExtensionDowngrade(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) @@ -1309,6 +821,22 @@ func TestClusterExtensionDowngrade(t *testing.T) { err = cl.Update(ctx, clusterExtension) require.NoError(t, err) + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.0.1", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.1", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.1"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, nil + } + // Run reconcile again res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.Error(t, err) @@ -1327,7 +855,7 @@ func TestClusterExtensionDowngrade(t *testing.T) { require.NotNil(t, cond) assert.Equal(t, metav1.ConditionFalse, cond.Status) assert.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Equal(t, "error upgrading from currently installed version \"1.0.1\": no package \"prometheus\" matching version \"1.0.0\" found in channel \"beta\"", cond.Message) + assert.Equal(t, "error upgrading from currently installed version \"1.0.1\": no package \"prometheus\" matching version \"1.0.0\" in channel \"beta\" found", cond.Message) }) } }) @@ -1350,7 +878,6 @@ func TestClusterExtensionDowngrade(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) @@ -1393,6 +920,22 @@ func TestClusterExtensionDowngrade(t *testing.T) { err = cl.Update(ctx, clusterExtension) require.NoError(t, err) + controllers.GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGetter, allBundles []*catalogmetadata.Bundle, ext *ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) { + return &catalogmetadata.Bundle{ + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/2.0.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake2.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, nil + } + // Run reconcile again res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey}) require.NoError(t, err) diff --git a/internal/controllers/clusterextension_registryv1_validation_test.go b/internal/controllers/clusterextension_registryv1_validation_test.go index 4339e768d..5588f7463 100644 --- a/internal/controllers/clusterextension_registryv1_validation_test.go +++ b/internal/controllers/clusterextension_registryv1_validation_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,7 +17,7 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" + "github.com/operator-framework/rukpak/pkg/source" ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" @@ -99,13 +100,19 @@ func TestClusterExtensionRegistryV1DisallowDependencies(t *testing.T) { t.Run(tt.name, func(t *testing.T) { defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{})) - require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha2.BundleDeployment{})) }() - fakeCatalogClient := testutil.NewFakeCatalogClient([]*catalogmetadata.Bundle{tt.bundle}) + mockUnpacker := unpacker.(*MockUnpacker) + // Set up the Unpack method to return a result with StatePending + mockUnpacker.On("Unpack", mock.Anything, mock.AnythingOfType("*v1alpha2.BundleDeployment")).Return(&source.Result{ + State: source.StatePending, + }, nil) + reconciler := &controllers.ClusterExtensionReconciler{ - Client: cl, - BundleProvider: &fakeCatalogClient, + Client: cl, + BundleProvider: &fakeCatalogClient, + ActionClientGetter: helmClientGetter, + Unpacker: unpacker, } installNamespace := fmt.Sprintf("test-ns-%s", rand.String(8)) diff --git a/internal/controllers/common_controller.go b/internal/controllers/common_controller.go index 8bf0b8e5f..7031d3a8f 100644 --- a/internal/controllers/common_controller.go +++ b/internal/controllers/common_controller.go @@ -22,6 +22,9 @@ import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" + "github.com/operator-framework/rukpak/pkg/source" + ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" ) @@ -54,6 +57,17 @@ func setInstalledStatusConditionUnknown(conditions *[]metav1.Condition, message }) } +// setHasValidBundleUnknown sets the installed status condition to unknown. +func setHasValidBundleUnknown(conditions *[]metav1.Condition, message string, generation int64) { + apimeta.SetStatusCondition(conditions, metav1.Condition{ + Type: ocv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionUnknown, + Reason: ocv1alpha1.ReasonHasValidBundleUnknown, + Message: message, + ObservedGeneration: generation, + }) +} + // setResolvedStatusConditionFailed sets the resolved status condition to failed. func setResolvedStatusConditionFailed(conditions *[]metav1.Condition, message string, generation int64) { apimeta.SetStatusCondition(conditions, metav1.Condition{ @@ -106,3 +120,46 @@ func setDeprecationStatusesUnknown(conditions *[]metav1.Condition, message strin }) } } + +func updateStatusUnpackFailing(status *ocv1alpha1.ClusterExtensionStatus, err error) error { + status.InstalledBundle = nil + apimeta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakv1alpha2.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakv1alpha2.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, generation int64) { + status.InstalledBundle = nil + apimeta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakv1alpha2.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakv1alpha2.ReasonUnpackPending, + Message: result.Message, + ObservedGeneration: generation, + }) +} + +// TODO: verify if we need to update the installBundle status or leave it as is. +func updateStatusUnpacking(status *ocv1alpha1.ClusterExtensionStatus, result *source.Result) { + status.InstalledBundle = nil + apimeta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakv1alpha2.TypeUnpacked, + Status: metav1.ConditionFalse, + Reason: rukpakv1alpha2.ReasonUnpacking, + Message: result.Message, + }) +} + +func updateStatusUnpacked(status *ocv1alpha1.ClusterExtensionStatus, result *source.Result) { + apimeta.SetStatusCondition(&status.Conditions, metav1.Condition{ + Type: rukpakv1alpha2.TypeUnpacked, + Status: metav1.ConditionTrue, + Reason: rukpakv1alpha2.ReasonUnpackSuccessful, + Message: result.Message, + }) +} diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index afa5746c3..a46e1bc6d 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -17,24 +17,78 @@ limitations under the License. package controllers_test import ( + "context" + "io/fs" "log" + "net/http" "os" "path/filepath" "testing" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" 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" + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "github.com/operator-framework/rukpak/api/v1alpha2" + "github.com/operator-framework/rukpak/pkg/source" + "github.com/operator-framework/rukpak/pkg/storage" + "github.com/operator-framework/operator-controller/internal/controllers" "github.com/operator-framework/operator-controller/pkg/scheme" testutil "github.com/operator-framework/operator-controller/test/util" ) +// MockUnpacker is a mock of Unpacker interface +type MockUnpacker struct { + mock.Mock +} + +// Unpack mocks the Unpack method +func (m *MockUnpacker) Unpack(ctx context.Context, bd *v1alpha2.BundleDeployment) (*source.Result, error) { + args := m.Called(ctx, bd) + return args.Get(0).(*source.Result), args.Error(1) +} + +// MockStorage is a mock of Storage interface +type MockStorage struct { + mock.Mock +} + +func (m *MockStorage) Load(ctx context.Context, owner client.Object) (fs.FS, error) { + args := m.Called(ctx, owner) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(fs.FS), args.Error(1) +} + +func (m *MockStorage) Delete(ctx context.Context, owner client.Object) error { + //TODO implement me + panic("implement me") +} + +func (m *MockStorage) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + //TODO implement me + panic("implement me") +} + +func (m *MockStorage) URLFor(ctx context.Context, owner client.Object) (string, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockStorage) Store(ctx context.Context, owner client.Object, bundle fs.FS) error { + args := m.Called(ctx, owner, bundle) + return args.Error(0) +} + func newClient(t *testing.T) client.Client { - cl, err := client.New(cfg, client.Options{Scheme: scheme.Scheme}) + cl, err := client.New(config, client.Options{Scheme: scheme.Scheme}) require.NoError(t, err) require.NotNil(t, cl) return cl @@ -44,31 +98,45 @@ func newClientAndReconciler(t *testing.T) (client.Client, *controllers.ClusterEx cl := newClient(t) fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) reconciler := &controllers.ClusterExtensionReconciler{ - Client: cl, - BundleProvider: &fakeCatalogClient, + Client: cl, + BundleProvider: &fakeCatalogClient, + ActionClientGetter: helmClientGetter, + Unpacker: unpacker, + Storage: store, } return cl, reconciler } var ( - cfg *rest.Config + config *rest.Config + helmClientGetter helmclient.ActionClientGetter + unpacker source.Unpacker // Interface, will be initialized as a mock in TestMain + store storage.Storage ) func TestMain(m *testing.M) { testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "config", "crd", "bases"), - filepath.Join("..", "..", "testdata", "crds")}, + filepath.Join("..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, } var err error - cfg, err = testEnv.Start() + config, err = testEnv.Start() utilruntime.Must(err) - if cfg == nil { + if config == nil { log.Panic("expected cfg to not be nil") } + rm := meta.NewDefaultRESTMapper(nil) + cfgGetter, err := helmclient.NewActionConfigGetter(config, rm) + utilruntime.Must(err) + helmClientGetter, err = helmclient.NewActionClientGetter(cfgGetter) + utilruntime.Must(err) + + unpacker = new(MockUnpacker) + store = new(MockStorage) + code := m.Run() utilruntime.Must(testEnv.Stop()) os.Exit(code) diff --git a/internal/handler/interfaces.go b/internal/handler/interfaces.go new file mode 100644 index 000000000..2bdd502af --- /dev/null +++ b/internal/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/handler/registry.go b/internal/handler/registry.go new file mode 100644 index 000000000..9761358f8 --- /dev/null +++ b/internal/handler/registry.go @@ -0,0 +1,105 @@ +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" + + "github.com/operator-framework/rukpak/pkg/convert" + "github.com/operator-framework/rukpak/pkg/util" + + ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" +) + +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.InstallNamespace, nil) + 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/labels/labels.go b/internal/labels/labels.go new file mode 100644 index 000000000..033f44e1e --- /dev/null +++ b/internal/labels/labels.go @@ -0,0 +1,13 @@ +package labels + +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. Which is why a similar format as OwnerKindKey/OwnerNameKey + // cannot be used as they do not conform to the regex requirements. + PackageNameKey = "olm_operatorframework_io_package_name" + BundleNameKey = "olm_operatorframework_io_bundle_name" + BundleVersionKey = "olm_operatorframework_io_bundle_version" +) diff --git a/scripts/install.tpl.sh b/scripts/install.tpl.sh index e6d4be2a6..7ee165004 100644 --- a/scripts/install.tpl.sh +++ b/scripts/install.tpl.sh @@ -11,19 +11,15 @@ fi catalogd_version=$CATALOGD_VERSION cert_mgr_version=$CERT_MGR_VERSION -rukpak_version=$RUKPAK_VERSION -if [[ -z "$catalogd_version" || -z "$cert_mgr_version" || -z "$rukpak_version" ]]; then +if [[ -z "$catalogd_version" || -z "$cert_mgr_version" ]]; then err="Error: Missing component version(s) for: " if [[ -z "$catalogd_version" ]]; then err+="catalogd " fi if [[ -z "$cert_mgr_version" ]]; then err+="cert-manager " - fi - if [[ -z "$rukpak_version" ]]; then - err+="rukpak " - fi + fi echo "$err" exit 1 fi @@ -39,11 +35,6 @@ function kubectl_wait() { kubectl apply -f "https://github.com/cert-manager/cert-manager/releases/download/${cert_mgr_version}/cert-manager.yaml" kubectl_wait "cert-manager" "deployment/cert-manager-webhook" "60s" -kubectl apply -f "https://github.com/operator-framework/rukpak/releases/download/${rukpak_version}/rukpak.yaml" -kubectl_wait "rukpak-system" "deployment/core" "60s" -kubectl_wait "rukpak-system" "deployment/helm-provisioner" "60s" -kubectl_wait "rukpak-system" "deployment/rukpak-webhooks" "60s" - kubectl apply -f "https://github.com/operator-framework/catalogd/releases/download/${catalogd_version}/catalogd.yaml" kubectl_wait "catalogd-system" "deployment/catalogd-controller-manager" "60s" diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index d7fc3c300..721a0f716 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -37,7 +37,7 @@ const ( var pollDuration = time.Minute var pollInterval = time.Second -func testInit(t *testing.T) (*ocv1alpha1.ClusterExtension, string, *catalogd.Catalog) { +func testInit(t *testing.T) (*ocv1alpha1.ClusterExtension, *catalogd.Catalog) { var err error extensionCatalog, err := createTestCatalog(context.Background(), testCatalogName, os.Getenv(testCatalogRefEnvVar)) require.NoError(t, err) @@ -48,7 +48,7 @@ func testInit(t *testing.T) (*ocv1alpha1.ClusterExtension, string, *catalogd.Cat Name: clusterExtensionName, }, } - return clusterExtension, clusterExtensionName, extensionCatalog + return clusterExtension, extensionCatalog } func testCleanup(t *testing.T, cat *catalogd.Catalog, clusterExtension *ocv1alpha1.ClusterExtension) { @@ -68,7 +68,7 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When the extension bundle format is registry+v1") - clusterExtension, clusterExtensionName, extensionCatalog := testInit(t) + clusterExtension, extensionCatalog := testInit(t) defer testCleanup(t, extensionCatalog, clusterExtension) defer getArtifactsOutput(t) @@ -83,7 +83,7 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { t.Log("By eventually reporting a successful resolution and bundle path") require.EventuallyWithT(t, func(ct *assert.CollectT) { assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - assert.Len(ct, clusterExtension.Status.Conditions, 6) + assert.Len(ct, clusterExtension.Status.Conditions, 8) cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) if !assert.NotNil(ct, cond) { return @@ -94,6 +94,18 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.2.0.0", Version: "2.0.0"}, clusterExtension.Status.ResolvedBundle) }, pollDuration, pollInterval) + t.Log("By eventually reporting a successful unpacked") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, rukpakv1alpha2.TypeUnpacked) + if !assert.NotNil(ct, cond) { + return + } + assert.Equal(ct, metav1.ConditionTrue, cond.Status) + assert.Equal(ct, rukpakv1alpha2.ReasonUnpackSuccessful, cond.Reason) + assert.Contains(ct, cond.Message, "Successfully unpacked") + }, pollDuration, pollInterval) + t.Log("By eventually installing the package successfully") require.EventuallyWithT(t, func(ct *assert.CollectT) { assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) @@ -103,21 +115,8 @@ func TestClusterExtensionInstallRegistry(t *testing.T) { } assert.Equal(ct, metav1.ConditionTrue, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) - assert.Contains(ct, cond.Message, "installed from") + assert.Contains(ct, cond.Message, "Instantiated bundle") assert.NotEmpty(ct, clusterExtension.Status.InstalledBundle) - - bd := rukpakv1alpha2.BundleDeployment{} - assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtensionName}, &bd)) - isUnpackSuccessful := apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha2.TypeUnpacked) - if !assert.NotNil(ct, isUnpackSuccessful) { - return - } - assert.Equal(ct, rukpakv1alpha2.ReasonUnpackSuccessful, isUnpackSuccessful.Reason) - installed := apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha2.TypeInstalled) - if !assert.NotNil(ct, installed) { - return - } - assert.Equal(ct, rukpakv1alpha2.ReasonInstallationSucceeded, installed.Reason) }, pollDuration, pollInterval) } @@ -125,7 +124,7 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("It resolves again when a new catalog is available") - clusterExtension, _, extensionCatalog := testInit(t) + clusterExtension, extensionCatalog := testInit(t) defer testCleanup(t, extensionCatalog, clusterExtension) defer getArtifactsOutput(t) @@ -154,7 +153,7 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) { } assert.Equal(ct, metav1.ConditionFalse, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Equal(ct, fmt.Sprintf("no package %q found", pkgName), cond.Message) + assert.Contains(ct, cond.Message, fmt.Sprintf("no package %q found", pkgName)) }, pollDuration, pollInterval) t.Log("By creating an ClusterExtension catalog with the desired package") @@ -187,7 +186,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, _, extensionCatalog := testInit(t) + clusterExtension, extensionCatalog := testInit(t) defer testCleanup(t, extensionCatalog, clusterExtension) defer getArtifactsOutput(t) @@ -198,7 +197,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { InstallNamespace: "default", } require.NoError(t, c.Create(context.Background(), clusterExtension)) - t.Log("By eventually reporting a successful resolution") + t.Log("By eventually reporting a successful installation") require.EventuallyWithT(t, func(ct *assert.CollectT) { assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved) @@ -208,6 +207,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason) assert.Contains(ct, cond.Message, "resolved to") assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.0", Version: "1.0.0"}, clusterExtension.Status.ResolvedBundle) + assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.0", Version: "1.0.0"}, clusterExtension.Status.InstalledBundle) }, pollDuration, pollInterval) t.Log("It does not allow to upgrade the ClusterExtension to a non-successor version") @@ -232,7 +232,7 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, _, extensionCatalog := testInit(t) + clusterExtension, extensionCatalog := testInit(t) defer testCleanup(t, extensionCatalog, clusterExtension) defer getArtifactsOutput(t) @@ -277,7 +277,7 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) { func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { t.Log("When a cluster extension is installed from a catalog") t.Log("When resolving upgrade edges") - clusterExtension, _, extensionCatalog := testInit(t) + clusterExtension, extensionCatalog := testInit(t) defer testCleanup(t, extensionCatalog, clusterExtension) defer getArtifactsOutput(t) @@ -323,7 +323,6 @@ func TestClusterExtensionInstallSuccessorVersion(t *testing.T) { // - clusterextensions // - pods logs // - deployments -// - bundledeployments // - catalogsources func getArtifactsOutput(t *testing.T) { basePath := env.GetString("ARTIFACT_PATH", "") @@ -383,23 +382,6 @@ func getArtifactsOutput(t *testing.T) { } } - // Get all BundleDeployments in the namespace and save them to the artifact path. - bundleDeployments := rukpakv1alpha2.BundleDeploymentList{} - if err := c.List(context.Background(), &bundleDeployments, client.InNamespace("")); err != nil { - fmt.Printf("Failed to list bundleDeployments: %v", err) - } - for _, bundleDeployment := range bundleDeployments.Items { - // Save bundleDeployment to artifact path - bundleDeploymentYaml, err := yaml.Marshal(bundleDeployment) - if err != nil { - fmt.Printf("Failed to marshal bundleDeployment: %v", err) - continue - } - if err := os.WriteFile(filepath.Join(artifactPath, bundleDeployment.Name+"-bundleDeployment.yaml"), bundleDeploymentYaml, 0600); err != nil { - fmt.Printf("Failed to write bundleDeployment to file: %v", err) - } - } - for _, namespace := range namespaces.Items { // let's ignore kube-* namespaces. if strings.Contains(namespace.Name, "kube-") { diff --git a/test/e2e/cluster_extension_registryV1_limitations_test.go b/test/e2e/cluster_extension_registryV1_limitations_test.go index 90705748a..824b1f686 100644 --- a/test/e2e/cluster_extension_registryV1_limitations_test.go +++ b/test/e2e/cluster_extension_registryV1_limitations_test.go @@ -2,60 +2,35 @@ package e2e import ( "context" - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" - ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" ) func TestClusterExtensionPackagesWithWebhooksAreNotAllowed(t *testing.T) { ctx := context.Background() - catalog, err := createTestCatalog(ctx, testCatalogName, os.Getenv(testCatalogRefEnvVar)) - defer func(cat *catalogd.Catalog) { - require.NoError(t, c.Delete(context.Background(), cat)) - require.Eventually(t, func() bool { - err := c.Get(context.Background(), types.NamespacedName{Name: cat.Name}, &catalogd.Catalog{}) - return errors.IsNotFound(err) - }, pollDuration, pollInterval) - }(catalog) - require.NoError(t, err) - - deleteClusterExtension := func(clusterExtension *ocv1alpha1.ClusterExtension) { - require.NoError(t, c.Delete(ctx, clusterExtension)) - require.Eventually(t, func() bool { - err := c.Get(ctx, types.NamespacedName{Name: clusterExtension.Name}, &ocv1alpha1.ClusterExtension{}) - return errors.IsNotFound(err) - }, pollDuration, pollInterval) - } - - clusterExtension := &ocv1alpha1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "package-with-webhooks-", - }, - Spec: ocv1alpha1.ClusterExtensionSpec{ - PackageName: "package-with-webhooks", - Version: "1.0.0", - InstallNamespace: "default", - }, + clusterExtension, catalog := testInit(t) + defer testCleanup(t, catalog, clusterExtension) + defer getArtifactsOutput(t) + + clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{ + PackageName: "package-with-webhooks", + Version: "1.0.0", + InstallNamespace: "default", } - err = c.Create(ctx, clusterExtension) - defer deleteClusterExtension(clusterExtension) - require.NoError(t, err) - + require.NoError(t, c.Create(ctx, clusterExtension)) require.EventuallyWithT(t, func(ct *assert.CollectT) { assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled) - assert.NotNil(ct, cond) + if !assert.NotNil(ct, cond) { + return + } assert.Equal(ct, metav1.ConditionFalse, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonInstallationFailed, cond.Reason) assert.Contains(ct, cond.Message, "webhookDefinitions are not supported") diff --git a/testdata/crds/core.rukpak.io_bundledeployments.yaml b/testdata/crds/core.rukpak.io_bundledeployments.yaml deleted file mode 100644 index c9a7e6f9e..000000000 --- a/testdata/crds/core.rukpak.io_bundledeployments.yaml +++ /dev/null @@ -1,488 +0,0 @@ -## TODO dfranz: remove this file and add a crds package in rukpak so we can grab it from that repo instead of having two copies ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - name: bundledeployments.core.rukpak.io -spec: - group: core.rukpak.io - names: - kind: BundleDeployment - listKind: BundleDeploymentList - plural: bundledeployments - shortNames: - - bd - - bds - singular: bundledeployment - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(.type=="Installed")].reason - name: Install State - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .spec.provisionerClassName - name: Provisioner - priority: 1 - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: BundleDeployment is the Schema for the bundledeployments API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - properties: - name: - maxLength: 52 - type: string - type: object - spec: - description: BundleDeploymentSpec defines the desired state of BundleDeployment - properties: - config: - description: config is provisioner specific configurations - type: object - x-kubernetes-preserve-unknown-fields: true - installNamespace: - description: |- - installNamespace is the namespace where the bundle should be installed. However, note that - the bundle may contain resources that are cluster-scoped or that are - installed in a different namespace. This namespace is expected to exist. - maxLength: 63 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - provisionerClassName: - description: provisionerClassName sets the name of the provisioner - that should reconcile this BundleDeployment. - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - source: - description: source defines the configuration for the underlying Bundle - content. - properties: - configMaps: - description: |- - ConfigMaps is a list of config map references and their relative - directory paths that represent a bundle filesystem. - items: - properties: - configMap: - description: ConfigMap is a reference to a configmap in - the rukpak system namespace - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - path: - description: |- - Path is the relative directory path within the bundle where the files - from the configmap will be present when the bundle is unpacked. - type: string - required: - - configMap - type: object - type: array - git: - description: Git is the git repository that backs the content - of this Bundle. - properties: - auth: - description: Auth configures the authorization method if necessary. - properties: - insecureSkipVerify: - description: |- - InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify - is true, the clone operation will accept any certificate presented by the server and any host name in that - certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is - used. This should be used only for testing. - type: boolean - secret: - description: |- - Secret contains reference to the secret that has authorization information and is in the namespace that the provisioner is deployed. - The secret is expected to contain `data.username` and `data.password` for the username and password, respectively for http(s) scheme. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret - For the ssh authorization of the GitSource, the secret is expected to contain `data.ssh-privatekey` and `data.ssh-knownhosts` for the ssh privatekey and the host entry in the known_hosts file respectively. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#ssh-authentication-secrets - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - type: object - directory: - description: |- - Directory refers to the location of the bundle within the git repository. - Directory is optional and if not set defaults to ./manifests. - type: string - ref: - description: |- - Ref configures the git source to clone a specific branch, tag, or commit - from the specified repo. Ref is required, and exactly one field within Ref - is required. Setting more than one field or zero fields will result in an - error. - oneOf: - - required: - - branch - - required: - - commit - - required: - - tag - properties: - branch: - description: |- - Branch refers to the branch to checkout from the repository. - The Branch should contain the bundle manifests in the specified directory. - type: string - commit: - description: |- - Commit refers to the commit to checkout from the repository. - The Commit should contain the bundle manifests in the specified directory. - type: string - tag: - description: |- - Tag refers to the tag to checkout from the repository. - The Tag should contain the bundle manifests in the specified directory. - type: string - type: object - repository: - description: |- - Repository is a URL link to the git repository containing the bundle. - Repository is required and the URL should be parsable by a standard git tool. - type: string - required: - - ref - - repository - type: object - http: - description: ' HTTP is the remote location that backs the content - of this Bundle.' - properties: - auth: - description: Auth configures the authorization method if necessary. - properties: - insecureSkipVerify: - description: |- - InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify - is true, the clone operation will accept any certificate presented by the server and any host name in that - certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is - used. This should be used only for testing. - type: boolean - secret: - description: |- - Secret contains reference to the secret that has authorization information and is in the namespace that the provisioner is deployed. - The secret is expected to contain `data.username` and `data.password` for the username and password, respectively for http(s) scheme. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret - For the ssh authorization of the GitSource, the secret is expected to contain `data.ssh-privatekey` and `data.ssh-knownhosts` for the ssh privatekey and the host entry in the known_hosts file respectively. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#ssh-authentication-secrets - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - type: object - url: - description: URL is where the bundle contents is. - type: string - required: - - url - type: object - image: - description: Image is the bundle image that backs the content - of this bundle. - properties: - pullSecret: - description: ImagePullSecretName contains the name of the - image pull secret in the namespace that the provisioner - is deployed. - type: string - ref: - description: Ref contains the reference to a container image - containing Bundle contents. - type: string - required: - - ref - type: object - type: - description: Type defines the kind of Bundle content being sourced. - type: string - required: - - type - type: object - required: - - installNamespace - - provisionerClassName - - source - type: object - status: - description: BundleDeploymentStatus defines the observed state of BundleDeployment - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - contentURL: - type: string - observedGeneration: - format: int64 - type: integer - resolvedSource: - properties: - configMaps: - description: |- - ConfigMaps is a list of config map references and their relative - directory paths that represent a bundle filesystem. - items: - properties: - configMap: - description: ConfigMap is a reference to a configmap in - the rukpak system namespace - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - path: - description: |- - Path is the relative directory path within the bundle where the files - from the configmap will be present when the bundle is unpacked. - type: string - required: - - configMap - type: object - type: array - git: - description: Git is the git repository that backs the content - of this Bundle. - properties: - auth: - description: Auth configures the authorization method if necessary. - properties: - insecureSkipVerify: - description: |- - InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify - is true, the clone operation will accept any certificate presented by the server and any host name in that - certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is - used. This should be used only for testing. - type: boolean - secret: - description: |- - Secret contains reference to the secret that has authorization information and is in the namespace that the provisioner is deployed. - The secret is expected to contain `data.username` and `data.password` for the username and password, respectively for http(s) scheme. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret - For the ssh authorization of the GitSource, the secret is expected to contain `data.ssh-privatekey` and `data.ssh-knownhosts` for the ssh privatekey and the host entry in the known_hosts file respectively. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#ssh-authentication-secrets - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - type: object - directory: - description: |- - Directory refers to the location of the bundle within the git repository. - Directory is optional and if not set defaults to ./manifests. - type: string - ref: - description: |- - Ref configures the git source to clone a specific branch, tag, or commit - from the specified repo. Ref is required, and exactly one field within Ref - is required. Setting more than one field or zero fields will result in an - error. - properties: - branch: - description: |- - Branch refers to the branch to checkout from the repository. - The Branch should contain the bundle manifests in the specified directory. - type: string - commit: - description: |- - Commit refers to the commit to checkout from the repository. - The Commit should contain the bundle manifests in the specified directory. - type: string - tag: - description: |- - Tag refers to the tag to checkout from the repository. - The Tag should contain the bundle manifests in the specified directory. - type: string - type: object - repository: - description: |- - Repository is a URL link to the git repository containing the bundle. - Repository is required and the URL should be parsable by a standard git tool. - type: string - required: - - ref - - repository - type: object - http: - description: ' HTTP is the remote location that backs the content - of this Bundle.' - properties: - auth: - description: Auth configures the authorization method if necessary. - properties: - insecureSkipVerify: - description: |- - InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If InsecureSkipVerify - is true, the clone operation will accept any certificate presented by the server and any host name in that - certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is - used. This should be used only for testing. - type: boolean - secret: - description: |- - Secret contains reference to the secret that has authorization information and is in the namespace that the provisioner is deployed. - The secret is expected to contain `data.username` and `data.password` for the username and password, respectively for http(s) scheme. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret - For the ssh authorization of the GitSource, the secret is expected to contain `data.ssh-privatekey` and `data.ssh-knownhosts` for the ssh privatekey and the host entry in the known_hosts file respectively. - Refer to https://kubernetes.io/docs/concepts/configuration/secret/#ssh-authentication-secrets - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? - type: string - type: object - x-kubernetes-map-type: atomic - type: object - url: - description: URL is where the bundle contents is. - type: string - required: - - url - type: object - image: - description: Image is the bundle image that backs the content - of this bundle. - properties: - pullSecret: - description: ImagePullSecretName contains the name of the - image pull secret in the namespace that the provisioner - is deployed. - type: string - ref: - description: Ref contains the reference to a container image - containing Bundle contents. - type: string - required: - - ref - type: object - type: - description: Type defines the kind of Bundle content being sourced. - type: string - required: - - type - type: object - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/tilt.md b/tilt.md index f9635d076..d1bd64cab 100644 --- a/tilt.md +++ b/tilt.md @@ -25,10 +25,10 @@ This ends up taking a fraction of the time, sometimes on the order of a few seco Follow Tilt's [instructions](https://docs.tilt.dev/install.html) for installation. -## Installing rukpak and catalogd +## Installing catalogd -operator-controller requires [rukpak](https://github.com/operator-framework/rukpak) and -[catalogd](https://github.com/operator-framework/catalogd). Please make sure they're installed, either normally or via +operator-controller requires +[catalogd](https://github.com/operator-framework/catalogd). Please make sure it's installed, either normally or via their own Tiltfiles, before proceeding. If you want to use Tilt, make sure you specify a unique `--port` flag to each `tilt up` invocation.