From 51001be1f5a77b77003bf0bd4f32dcb55c196b5a Mon Sep 17 00:00:00 2001 From: Luca Burgazzoli Date: Tue, 15 Oct 2024 23:21:31 +0200 Subject: [PATCH] feat: kustomize renderer pipeline --- .../components/dashboard_controller.go | 166 ++++++------- go.mod | 56 +++-- go.sum | 131 +++++++---- pkg/cluster/gvk/gvk.go | 6 + pkg/controller/actions/action_deploy.go | 114 +++++++++ pkg/controller/actions/action_fn.go | 35 +++ .../actions/action_render_manifests.go | 162 +++++++++++++ .../actions/action_render_manifests_test.go | 219 ++++++++++++++++++ pkg/controller/actions/action_test.go | 2 +- .../actions/action_update_status.go | 15 +- .../reconciler/component_reconciler.go | 2 +- pkg/controller/types/types.go | 28 ++- pkg/manifests/kustomize/kustomize.go | 28 +++ pkg/manifests/kustomize/kustomize_engine.go | 82 +++++++ pkg/manifests/kustomize/kustomize_filters.go | 28 +++ pkg/manifests/kustomize/kustomize_opts.go | 20 ++ .../kustomize/kustomize_render_opts.go | 101 ++++++++ pkg/manifests/kustomize/kustomize_support.go | 16 ++ pkg/manifests/kustomize/kustomize_test.go | 68 ++++++ pkg/plugins/addAnnotationsplugin.go | 20 ++ pkg/plugins/removerplugin.go | 38 ++- pkg/utils/test/matchers/jq/jq_matcher.go | 60 +++++ pkg/utils/test/matchers/jq/jq_matcher_test.go | 89 +++++++ pkg/utils/test/matchers/jq/jq_support.go | 129 +++++++++++ pkg/utils/test/matchers/jq/jq_support_test.go | 54 +++++ pkg/utils/test/matchers/jq/jq_transform.go | 34 +++ .../test/matchers/jq/jq_transform_test.go | 31 +++ pkg/utils/test/matchers/yq/yq.go | 12 + pkg/utils/test/matchers/yq/yq_matcher.go | 60 +++++ pkg/utils/test/matchers/yq/yq_matcher_test.go | 62 +++++ pkg/utils/test/matchers/yq/yq_support.go | 106 +++++++++ .../test/matchers/yq/yq_support_logging.go | 13 ++ pkg/utils/test/matchers/yq/yq_transform.go | 35 +++ .../test/matchers/yq/yq_transform_test.go | 40 ++++ 34 files changed, 1892 insertions(+), 170 deletions(-) create mode 100644 pkg/controller/actions/action_deploy.go create mode 100644 pkg/controller/actions/action_fn.go create mode 100644 pkg/controller/actions/action_render_manifests.go create mode 100644 pkg/controller/actions/action_render_manifests_test.go create mode 100644 pkg/manifests/kustomize/kustomize.go create mode 100644 pkg/manifests/kustomize/kustomize_engine.go create mode 100644 pkg/manifests/kustomize/kustomize_filters.go create mode 100644 pkg/manifests/kustomize/kustomize_opts.go create mode 100644 pkg/manifests/kustomize/kustomize_render_opts.go create mode 100644 pkg/manifests/kustomize/kustomize_support.go create mode 100644 pkg/manifests/kustomize/kustomize_test.go create mode 100644 pkg/plugins/addAnnotationsplugin.go create mode 100644 pkg/utils/test/matchers/jq/jq_matcher.go create mode 100644 pkg/utils/test/matchers/jq/jq_matcher_test.go create mode 100644 pkg/utils/test/matchers/jq/jq_support.go create mode 100644 pkg/utils/test/matchers/jq/jq_support_test.go create mode 100644 pkg/utils/test/matchers/jq/jq_transform.go create mode 100644 pkg/utils/test/matchers/jq/jq_transform_test.go create mode 100644 pkg/utils/test/matchers/yq/yq.go create mode 100644 pkg/utils/test/matchers/yq/yq_matcher.go create mode 100644 pkg/utils/test/matchers/yq/yq_matcher_test.go create mode 100644 pkg/utils/test/matchers/yq/yq_support.go create mode 100644 pkg/utils/test/matchers/yq/yq_support_logging.go create mode 100644 pkg/utils/test/matchers/yq/yq_transform.go create mode 100644 pkg/utils/test/matchers/yq/yq_transform_test.go diff --git a/controllers/components/dashboard_controller.go b/controllers/components/dashboard_controller.go index e8084df11d6..c6eb3fac4a9 100644 --- a/controllers/components/dashboard_controller.go +++ b/controllers/components/dashboard_controller.go @@ -18,9 +18,7 @@ package components import ( "context" - "errors" "fmt" - "path/filepath" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -45,7 +43,7 @@ import ( odhrec "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/reconciler" odhtypes "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" - ctrlogger "github.com/opendatahub-io/opendatahub-operator/v2/pkg/logger" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/manifests/kustomize" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/metadata/labels" ) @@ -73,17 +71,36 @@ func NewDashboardReconciler(ctx context.Context, mgr ctrl.Manager) error { } actionCtx := logf.IntoContext(ctx, r.Log) + d := Dashboard{} + // Add Dashboard-specific actions - r.AddAction(&InitializeAction{actions.BaseAction{Log: mgr.GetLogger().WithName("actions").WithName("initialize")}}) - r.AddAction(&SupportDevFlagsAction{actions.BaseAction{Log: mgr.GetLogger().WithName("actions").WithName("devFlags")}}) - r.AddAction(&CleanupOAuthClientAction{actions.BaseAction{Log: mgr.GetLogger().WithName("actions").WithName("cleanup")}}) - r.AddAction(&DeployComponentAction{actions.BaseAction{Log: mgr.GetLogger().WithName("actions").WithName("deploy")}}) + r.AddAction(actions.NewActionFn(actionCtx, d.initialize)) + r.AddAction(actions.NewActionFn(actionCtx, d.devFlags)) + + r.AddAction(actions.NewRenderManifestsAction( + actionCtx, + actions.WithRenderManifestsOptions( + kustomize.WithEngineRenderOpts( + kustomize.WithLabel(labels.ComponentName, ComponentName), + ), + ), + )) + + r.AddAction(actions.NewActionFn(actionCtx, d.customizeResources)) + + r.AddAction(actions.NewDeployAction( + actionCtx, + actions.WithDeployedResourceFieldOwner(ComponentName), + )) r.AddAction(actions.NewUpdateStatusAction( actionCtx, actions.WithUpdateStatusLabel(labels.ComponentName, ComponentName), )) + // finalization + r.AddFinalizer(actions.NewActionFn(ctx, d.cleanupOAuthClient)) + eh := handler.EnqueueRequestsFromMapFunc(watchDashboardResources) err = ctrl.NewControllerManagedBy(mgr). @@ -101,6 +118,7 @@ func NewDashboardReconciler(ctx context.Context, mgr ctrl.Manager) error { Watches(&corev1.ServiceAccount{}, eh). // shared filter WithEventFilter(dashboardPredicates). + // done Complete(r) if err != nil { @@ -166,11 +184,12 @@ func CreateDashboardInstance(dsc *dscv1.DataScienceCluster) *componentsv1.Dashbo // +kubebuilder:rbac:groups="*",resources=replicasets,verbs=* func watchDashboardResources(_ context.Context, a client.Object) []reconcile.Request { - if a.GetLabels()["app.opendatahub.io/dashboard"] == "true" { + if a.GetLabels()[labels.ODH.Component(ComponentNameUpstream)] == "true" || a.GetLabels()[labels.ODH.Component(ComponentNameDownstream)] == "true" { return []reconcile.Request{{ NamespacedName: types.NamespacedName{Name: DashboardInstanceName}, }} } + return nil } @@ -200,6 +219,7 @@ var dashboardPredicates = predicate.Funcs{ }, } +//nolint:unused func updateKustomizeVariable(ctx context.Context, cli client.Client, platform cluster.Platform, dscispec *dsciv1.DSCInitializationSpec) (map[string]string, error) { adminGroups := map[cluster.Platform]string{ cluster.SelfManagedRhods: "rhods-admins", @@ -233,13 +253,11 @@ func updateKustomizeVariable(ctx context.Context, cli client.Client, platform cl }, nil } -// Action implementations +// TODO added only to avoid name collision -type InitializeAction struct { - actions.BaseAction -} +type Dashboard struct{} -func (a *InitializeAction) Execute(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { +func (d Dashboard) initialize(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { // Implement initialization logic log := logf.FromContext(ctx).WithName(ComponentNameUpstream) @@ -254,7 +272,20 @@ func (a *InitializeAction) Execute(ctx context.Context, rr *odhtypes.Reconciliat } DefaultPath = manifestMap[rr.Platform] - rr.Manifests = manifestMap + componentName := ComponentNameDownstream + if rr.Platform == cluster.SelfManagedRhods || rr.Platform == cluster.ManagedRhods { + componentName = ComponentNameDownstream + } + + rr.Manifests = []odhtypes.ManifestInfo{{ + Path: DefaultPath, + ContextDir: "", + SourcePath: "", + RenderOpts: []kustomize.RenderOptsFn{ + kustomize.WithLabel(labels.ODH.Component(componentName), "true"), + kustomize.WithLabel(labels.K8SCommon.PartOf, componentName), + }, + }} if err := deploy.ApplyParams(DefaultPath, imageParamMap); err != nil { log.Error(err, "failed to update image", "path", DefaultPath) @@ -263,11 +294,7 @@ func (a *InitializeAction) Execute(ctx context.Context, rr *odhtypes.Reconciliat return nil } -type SupportDevFlagsAction struct { - actions.BaseAction -} - -func (a *SupportDevFlagsAction) Execute(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { +func (d Dashboard) devFlags(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { dashboard, ok := rr.Instance.(*componentsv1.Dashboard) if !ok { return fmt.Errorf("resource instance %v is not a componentsv1.Dashboard)", rr.Instance) @@ -284,84 +311,40 @@ func (a *SupportDevFlagsAction) Execute(ctx context.Context, rr *odhtypes.Reconc return err } if manifestConfig.SourcePath != "" { - rr.Manifests[rr.Platform] = filepath.Join(deploy.DefaultManifestPath, ComponentNameUpstream, manifestConfig.SourcePath) + rr.Manifests[0].Path = deploy.DefaultManifestPath + rr.Manifests[0].ContextDir = ComponentNameUpstream + rr.Manifests[0].SourcePath = manifestConfig.SourcePath } } - if rr.DSCI.Spec.DevFlags != nil { - mode := rr.DSCI.Spec.DevFlags.LogMode - a.Log = ctrlogger.NewNamedLogger(logf.FromContext(ctx), ComponentName, mode) - } - return nil } -type CleanupOAuthClientAction struct { - actions.BaseAction -} +func (d Dashboard) cleanupOAuthClient(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { + oauthClientSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dashboard-oauth-client", + Namespace: rr.DSCI.Spec.ApplicationsNamespace, + }, + } -func (a *CleanupOAuthClientAction) Execute(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { - // Remove previous oauth-client secrets - // Check if component is going from state of `Not Installed --> Installed` - // Assumption: Component is currently set to enabled - name := "dashboard-oauth-client" - - // r.Log.Info("Cleanup any left secret") - // Delete client secrets from previous installation - oauthClientSecret := &corev1.Secret{} - err := rr.Client.Get(ctx, client.ObjectKey{ - Namespace: rr.DSCI.Spec.ApplicationsNamespace, - Name: name, - }, oauthClientSecret) - if err != nil { - if !k8serr.IsNotFound(err) { - return fmt.Errorf("error getting secret %s: %w", name, err) - } - } else { - if err := rr.Client.Delete(ctx, oauthClientSecret); err != nil { - return fmt.Errorf("error deleting secret %s: %w", name, err) - } - // r.Log.Info("successfully deleted secret", "secret", name) + err := rr.Client.Delete(ctx, &oauthClientSecret) + if err != nil && !k8serr.IsNotFound(err) { + return fmt.Errorf("error deleting secret %s: %w", oauthClientSecret.Name, err) } return nil } -type DeployComponentAction struct { - actions.BaseAction -} - -func (a *DeployComponentAction) Execute(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { - // Implement component deployment logic - // 1. platform specific RBAC - if rr.Platform == cluster.OpenDataHub || rr.Platform == "" { - if err := cluster.UpdatePodSecurityRolebinding(ctx, rr.Client, rr.DSCI.Spec.ApplicationsNamespace, "odh-dashboard"); err != nil { - return err - } - } else { +func (d Dashboard) customizeResources(ctx context.Context, rr *odhtypes.ReconciliationRequest) error { + switch rr.Platform { + case cluster.SelfManagedRhods, cluster.ManagedRhods: if err := cluster.UpdatePodSecurityRolebinding(ctx, rr.Client, rr.DSCI.Spec.ApplicationsNamespace, "rhods-dashboard"); err != nil { return err } - } - - // 2. Append or Update variable for component to consume - _, err := updateKustomizeVariable(ctx, rr.Client, rr.Platform, &rr.DSCI.Spec) - if err != nil { - return errors.New("failed to set variable for extraParamsMap") - } - - // 3. update params.env regardless devFlags is provided of not - // if err := deploy.ApplyParams(r.entryPath, nil, extraParamsMap); err != nil { - // return fmt.Errorf("failed to update params.env from %s : %w", r.entryPath, err) - // } - - path := rr.Manifests[rr.Platform] - name := ComponentNameUpstream - - // common: Deploy odh-dashboard manifests - // TODO: check if we can have the same component name odh-dashboard for both, or still keep rhods-dashboard for RHOAI - switch rr.Platform { - case cluster.SelfManagedRhods, cluster.ManagedRhods: + // TODO this should be added to rr.Resources and let the final deploy action + // to apply the resource to the cluster + // // anaconda blckownerDel := true ctrlr := true @@ -381,28 +364,19 @@ func (a *DeployComponentAction) Execute(ctx context.Context, rr *odhtypes.Reconc }), cluster.WithLabels( labels.ComponentName, ComponentName, - labels.ODH.Component(name), "true", - labels.K8SCommon.PartOf, name, + labels.ODH.Component(ComponentNameUpstream), "true", + labels.K8SCommon.PartOf, ComponentNameUpstream, ), ) if err != nil { return fmt.Errorf("failed to create access-secret for anaconda: %w", err) } - - name = ComponentNameDownstream default: + if err := cluster.UpdatePodSecurityRolebinding(ctx, rr.Client, rr.DSCI.Spec.ApplicationsNamespace, "odh-dashboard"); err != nil { + return err + } } - err = deploy.DeployManifestsFromPathWithLabels(ctx, rr.Client, rr.Instance, path, rr.DSCI.Spec.ApplicationsNamespace, name, true, map[string]string{ - labels.ComponentName: ComponentName, - }) - - if err != nil { - return fmt.Errorf("failed to apply manifests from %s: %w", name, err) - } - - a.Log.Info("apply manifests done") - return nil } diff --git a/go.mod b/go.mod index cba7ae80440..0d5a6fbea20 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.21 require ( github.com/blang/semver/v4 v4.0.0 - github.com/go-logr/logr v1.4.1 + github.com/go-logr/logr v1.4.2 + github.com/goccy/go-yaml v1.12.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/itchyny/gojq v0.12.16 + github.com/mikefarah/yq/v4 v4.44.3 github.com/onsi/ginkgo v1.16.4 github.com/onsi/ginkgo/v2 v2.14.0 github.com/onsi/gomega v1.30.0 @@ -17,15 +20,17 @@ require ( github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.68.0 github.com/rs/xid v1.6.0 github.com/spf13/afero v1.10.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.29.2 k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 k8s.io/client-go v11.0.0+incompatible k8s.io/kube-aggregator v0.28.3 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 sigs.k8s.io/controller-runtime v0.17.5 sigs.k8s.io/kustomize/api v0.13.4 sigs.k8s.io/kustomize/kyaml v0.16.0 @@ -33,12 +38,17 @@ require ( ) require ( + github.com/a8m/envsubst v1.4.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/elliotchance/orderedmap v1.6.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -46,56 +56,62 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/goccy/go-json v0.10.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/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.7 // 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/matttproud/golang_protobuf_extensions/v2 v2.0.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/nxadm/tail v1.4.8 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.61.1-rhobs1 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/sergi/go-diff v1.2.0 // indirect github.com/sirupsen/logrus v1.9.2 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.24.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.29.2 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 4605b86184c..7128c01b532 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,14 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg= +github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY= +github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= +github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -61,8 +69,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elliotchance/orderedmap v1.6.0 h1:xjn+kbbKXeDq6v9RVE+WYwRbYfAZKvlWfcJNxM8pvEw= +github.com/elliotchance/orderedmap v1.6.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -75,11 +87,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.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/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -98,8 +111,8 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/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.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -117,9 +130,19 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -151,9 +174,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw 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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -197,9 +219,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= @@ -207,18 +228,25 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= +github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= 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= @@ -238,13 +266,24 @@ 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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mikefarah/yq/v4 v4.44.3 h1:3zxHntH67maSHr6ynCjM44htw7LZNINmTzYn3tM2t+I= +github.com/mikefarah/yq/v4 v4.44.3/go.mod h1:1pm9sJoyZLDql3OqgklvRCkD0XIIHMZV38jKZgAuxwY= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -310,6 +349,9 @@ github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPf github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= github.com/operator-framework/api v0.18.0 h1:6EdSNeAjin4LRu2YQnQWMJMc6HXS0AQDG+CfaEvFrAo= github.com/operator-framework/api v0.18.0/go.mod h1:SCCslqke6AVOJ5JM+NqNE1CHuAgJLScsL66pnPaSMXs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -331,13 +373,12 @@ github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.61.1-rhobs1 h1:s github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.61.1-rhobs1/go.mod h1:u8ctCYj9Nq8gkMLfNLxHoslu8SEGrqXP2gFiMUNsn9g= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -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/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -348,8 +389,9 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.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= @@ -359,8 +401,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -371,6 +414,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -395,6 +440,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -405,8 +452,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -494,8 +541,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -505,8 +552,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= 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= @@ -575,6 +622,7 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -584,8 +632,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -597,8 +645,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -608,7 +656,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -616,14 +663,13 @@ 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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/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= @@ -685,12 +731,13 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= @@ -719,9 +766,8 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -790,8 +836,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -802,6 +848,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE= +gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -814,7 +862,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -857,8 +904,8 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdz k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/cluster/gvk/gvk.go b/pkg/cluster/gvk/gvk.go index 5f2e0229bb4..2563cdbfff0 100644 --- a/pkg/cluster/gvk/gvk.go +++ b/pkg/cluster/gvk/gvk.go @@ -127,4 +127,10 @@ var ( Version: "v1", Kind: "TrainingOperator", } + + CustomResourceDefinition = schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + } ) diff --git a/pkg/controller/actions/action_deploy.go b/pkg/controller/actions/action_deploy.go new file mode 100644 index 00000000000..7d0ac234851 --- /dev/null +++ b/pkg/controller/actions/action_deploy.go @@ -0,0 +1,114 @@ +package actions + +import ( + "context" + "encoding/json" + + k8serr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster/gvk" + odhTypes "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" +) + +const ( + DeployActionName = "deploy" +) + +// DeployAction deploys the resources that are included in the ReconciliationRequest using +// the same create or patch machinery implemented as part of deploy.DeployManifestsFromPath +// +// TODO: we should support full Server-Side Apply. +type DeployAction struct { + BaseAction + fieldOwner string +} + +type DeployActionOpts func(*DeployAction) + +func WithDeployedResourceFieldOwner(value string) DeployActionOpts { + return func(action *DeployAction) { + action.fieldOwner = value + } +} + +func (r *DeployAction) Execute(ctx context.Context, rr *odhTypes.ReconciliationRequest) error { + for i := range rr.Resources { + obj := rr.Resources[i] + + switch obj.GroupVersionKind() { + case gvk.CustomResourceDefinition: + // do nothing + break + case gvk.OdhApplication: + // do nothing + break + default: + if err := ctrl.SetControllerReference(rr.Instance, &obj, rr.Client.Scheme()); err != nil { + return err + } + } + + err := rr.Client.Create(ctx, &rr.Resources[i]) + if k8serr.IsAlreadyExists(err) { + switch obj.GroupVersionKind() { + case gvk.OdhApplication: + // do nothing + break + default: + err = r.patch(ctx, rr.Client, obj) + } + } + + if err != nil { + return err + } + } + + return nil +} + +func (r *DeployAction) patch(ctx context.Context, c client.Client, obj unstructured.Unstructured) error { + found := unstructured.Unstructured{} + err := c.Get(ctx, client.ObjectKeyFromObject(&obj), &found) + if err != nil { + return err + } + + data, err := json.Marshal(obj) + if err != nil { + return err + } + + err = c.Patch( + ctx, + &found, + client.RawPatch(types.ApplyPatchType, data), + client.ForceOwnership, + client.FieldOwner(r.fieldOwner), + ) + + if err != nil { + return err + } + + return nil +} + +func NewDeployAction(ctx context.Context, opts ...DeployActionOpts) *DeployAction { + action := DeployAction{ + BaseAction: BaseAction{ + Log: log.FromContext(ctx).WithName(ActionGroup).WithName(DeployActionName), + }, + } + + for _, opt := range opts { + opt(&action) + } + + return &action +} diff --git a/pkg/controller/actions/action_fn.go b/pkg/controller/actions/action_fn.go new file mode 100644 index 00000000000..8ea99c1a91f --- /dev/null +++ b/pkg/controller/actions/action_fn.go @@ -0,0 +1,35 @@ +package actions + +import ( + "context" + "reflect" + "runtime" + + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" +) + +type ActionFn func(ctx context.Context, rr *types.ReconciliationRequest) error + +type WrapperAction struct { + BaseAction + fn ActionFn +} + +func (r *WrapperAction) Execute(ctx context.Context, rr *types.ReconciliationRequest) error { + return r.fn(ctx, rr) +} + +func NewActionFn(ctx context.Context, fn ActionFn) *WrapperAction { + name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() + + action := WrapperAction{ + BaseAction: BaseAction{ + Log: log.FromContext(ctx).WithName(ActionGroup).WithName(name), + }, + fn: fn, + } + + return &action +} diff --git a/pkg/controller/actions/action_render_manifests.go b/pkg/controller/actions/action_render_manifests.go new file mode 100644 index 00000000000..3af01dac14f --- /dev/null +++ b/pkg/controller/actions/action_render_manifests.go @@ -0,0 +1,162 @@ +package actions + +import ( + "context" + + k8serr "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster/gvk" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/manifests/kustomize" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/metadata/annotations" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/plugins" +) + +const ( + RenderManifestsActionName = "render-manifests" +) + +type RenderManifestsAction struct { + BaseAction + + keOpts []kustomize.EngineOptsFn + ke *kustomize.Engine +} + +type RenderManifestsActionOpts func(*RenderManifestsAction) + +func WithRenderManifestsOptions(values ...kustomize.EngineOptsFn) RenderManifestsActionOpts { + return func(action *RenderManifestsAction) { + action.keOpts = append(action.keOpts, values...) + } +} + +func (r *RenderManifestsAction) Execute(ctx context.Context, rr *types.ReconciliationRequest) error { + for i := range rr.Manifests { + opts := make([]kustomize.RenderOptsFn, 0, len(rr.Manifests[i].RenderOpts)+3) + opts = append(opts, kustomize.WithNamespace(rr.DSCI.Spec.ApplicationsNamespace)) + opts = append(opts, kustomize.WithFilter(filterUnmanged(ctx, rr))) + opts = append(opts, rr.Manifests[i].RenderOpts...) + opts = append(opts, kustomize.WithFilter(applyAllowList(ctx, rr))) + + resources, err := r.ke.Render( + rr.Manifests[i].ManifestsPath(), + opts..., + ) + + if err != nil { + return err + } + + rr.Resources = append(rr.Resources, resources...) + } + + return nil +} + +func filterUnmanged(ctx context.Context, rr *types.ReconciliationRequest) kustomize.FilterFn { + return func(nodes []*kyaml.RNode) ([]*kyaml.RNode, error) { + result := make([]*kyaml.RNode, 0, len(nodes)) + + for i := range nodes { + // TODO we should use PartialObjectMetadata to only retrieve the resource + // metadata since we do not need anything more, but this requires the + // cache to be reconfigured according. Some resources should still be + // fully cached (i.e. Deployments) + // po := metav1.PartialObjectMetadata{} + // po.SetGroupVersionKind(obj.GroupVersionKind()) + u := kustomize.NodeToUnstructured(nodes[i]) + + err := rr.Client.Get(ctx, client.ObjectKeyFromObject(&u), &u) + if err != nil && !k8serr.IsNotFound(err) { + return nodes, err + } + + // if the resource is found and not managed by the operator, remove it from + // the resources + if err == nil && u.GetAnnotations()[annotations.ManagedByODHOperator] == "false" { + continue + } + + result = append(result, nodes[i]) + } + + return result, nil + } +} + +func applyAllowList(ctx context.Context, rr *types.ReconciliationRequest) kustomize.FilterFn { + fieldsToClean := [][]string{ + {"spec", "template", "spec", "containers", "*", "resources"}, + {"spec", "replicas"}, + } + + return func(nodes []*kyaml.RNode) ([]*kyaml.RNode, error) { + for i := range nodes { + // TODO we should use PartialObjectMetadata to only retrieve the resource + // metadata since we do not need anything more, but this requires the + // cache to be reconfigured according. Some resources should still be + // fully cached (i.e. Deployments) + // po := metav1.PartialObjectMetadata{} + // po.SetGroupVersionKind(obj.GroupVersionKind()) + u := kustomize.NodeToUnstructured(nodes[i]) + + if u.GroupVersionKind() != gvk.Deployment { + continue + } + + err := rr.Client.Get(ctx, client.ObjectKeyFromObject(&u), &u) + if err != nil && !k8serr.IsNotFound(err) { + return nodes, err + } + + // If the resource does not exist, then don't apply the allow list + // as the resource must be created + if k8serr.IsNotFound(err) { + continue + } + + // If the resource is forcefully marked as managed by the operator, don't apply + // the allow list as the user is explicitly forcing the resource to be managed + // i.e. to restore default values + if u.GetAnnotations()[annotations.ManagedByODHOperator] == "true" { + continue + } + + // To preserve backward compatibility with the current model, fields are being + // removed, hence not included in the final PATCH. Ideally with should leverage + // Server-Side Apply [1] and copy the values from the actual resource or even + // better, make it possible to change operand's manifests only via the platform + // APIs. + // + // [1] https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts + for _, fields := range fieldsToClean { + nodes[i], err = plugins.ClearField(nodes[i], fields) + if err != nil { + return nil, err + } + } + } + + return nodes, nil + } +} + +func NewRenderManifestsAction(ctx context.Context, opts ...RenderManifestsActionOpts) *RenderManifestsAction { + action := RenderManifestsAction{ + BaseAction: BaseAction{ + Log: log.FromContext(ctx).WithName(ActionGroup).WithName(RenderManifestsActionName), + }, + } + + for _, opt := range opts { + opt(&action) + } + + action.ke = kustomize.NewEngine(action.keOpts...) + + return &action +} diff --git a/pkg/controller/actions/action_render_manifests_test.go b/pkg/controller/actions/action_render_manifests_test.go new file mode 100644 index 00000000000..47c0486818c --- /dev/null +++ b/pkg/controller/actions/action_render_manifests_test.go @@ -0,0 +1,219 @@ +package actions_test + +import ( + "context" + "path" + "testing" + + "github.com/rs/xid" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/kustomize/kyaml/filesys" + + dscv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/datasciencecluster/v1" + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster/gvk" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/actions" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/manifests/kustomize" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/jq" + + . "github.com/onsi/gomega" +) + +const testRenderResourcesKustomization = ` +apiVersion: kustomize.config.k8s.io/v1beta1 +resources: +- test-resources-cm.yaml +- test-resources-deployment-managed.yaml +- test-resources-deployment-unmanaged.yaml +- test-resources-deployment-forced.yaml +` + +const testRenderResourcesConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm +data: + foo: bar +` + +const testRenderResourcesManaged = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-managed +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.14.2 + resources: + limits: + memory: 200Mi + cpu: 1 + requests: + memory: 100Mi + cpu: 100m +` + +const testRenderResourcesUnmanaged = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-unmanaged +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.14.2 + resources: + limits: + memory: 200Mi + cpu: 1 + requests: + memory: 100Mi + cpu: 100m +` +const testRenderResourcesForced = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deployment-forced +spec: + replicas: 3 + template: + spec: + containers: + - name: nginx + image: nginx:1.14.2 +` + +func TestRenderResourcesAction(t *testing.T) { + g := NewWithT(t) + + ctx := context.Background() + ns := xid.New().String() + id := xid.New().String() + fs := filesys.MakeFsInMemory() + + _ = fs.MkdirAll(path.Join(id, kustomize.DefaultKustomizationFilePath)) + _ = fs.WriteFile(path.Join(id, kustomize.DefaultKustomizationFileName), []byte(testRenderResourcesKustomization)) + _ = fs.WriteFile(path.Join(id, "test-resources-cm.yaml"), []byte(testRenderResourcesConfigMap)) + _ = fs.WriteFile(path.Join(id, "test-resources-deployment-managed.yaml"), []byte(testRenderResourcesManaged)) + _ = fs.WriteFile(path.Join(id, "test-resources-deployment-unmanaged.yaml"), []byte(testRenderResourcesUnmanaged)) + _ = fs.WriteFile(path.Join(id, "test-resources-deployment-forced.yaml"), []byte(testRenderResourcesForced)) + + client := NewFakeClient( + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gvk.Deployment.GroupVersion().String(), + Kind: gvk.Deployment.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-deployment-managed", + Namespace: ns, + Labels: map[string]string{}, + }, + }, + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gvk.Deployment.GroupVersion().String(), + Kind: gvk.Deployment.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-deployment-unmanaged", + Namespace: ns, + Annotations: map[string]string{ + "opendatahub.io/managed": "false", + }, + }, + }, + &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gvk.Deployment.GroupVersion().String(), + Kind: gvk.Deployment.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-deployment-forced", + Namespace: ns, + Annotations: map[string]string{ + "opendatahub.io/managed": "true", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To[int32](1), + }, + }, + ) + + action := actions.NewRenderManifestsAction( + ctx, + actions.WithRenderManifestsOptions( + kustomize.WithEngineFS(fs), + kustomize.WithEngineRenderOpts( + kustomize.WithAnnotations(map[string]string{ + "platform.opendatahub.io/release": "1.2.3", + "platform.opendatahub.io/type": "managed", + }), + ), + ), + ) + + rr := types.ReconciliationRequest{ + Client: client, + Instance: nil, + DSCI: &dsciv1.DSCInitialization{Spec: dsciv1.DSCInitializationSpec{ApplicationsNamespace: ns}}, + DSC: &dscv1.DataScienceCluster{}, + Platform: cluster.OpenDataHub, + Manifests: []types.ManifestInfo{ + { + Path: id, + RenderOpts: []kustomize.RenderOptsFn{ + kustomize.WithLabel("component.opendatahub.io/name", "foo"), + kustomize.WithLabel("platform.opendatahub.io/namespace", ns), + }, + }, + }, + } + + err := action.Execute(ctx, &rr) + + g.Expect(err).ShouldNot(HaveOccurred()) + + // common customizations + g.Expect(rr.Resources).Should(And( + HaveLen(3), + HaveEach(And( + jq.Match(`.metadata.namespace == "%s"`, ns), + jq.Match(`.metadata.labels."component.opendatahub.io/name" == "%s"`, "foo"), + jq.Match(`.metadata.labels."platform.opendatahub.io/namespace" == "%s"`, ns), + jq.Match(`.metadata.annotations."platform.opendatahub.io/release" == "%s"`, "1.2.3"), + jq.Match(`.metadata.annotations."platform.opendatahub.io/type" == "%s"`, "managed"), + )), + )) + + // config map + g.Expect(rr.Resources[0]).Should(And( + jq.Match(`.metadata.name == "%s"`, "test-cm"), + )) + + // deployment managed + g.Expect(rr.Resources[1]).Should(And( + jq.Match(`.metadata.name == "%s"`, "test-deployment-managed"), + jq.Match(`.spec.template.spec.containers[0] | has("resources") | not`), + )) + + // deployment forced + g.Expect(rr.Resources[2]).Should(And( + jq.Match(`.metadata.name == "%s"`, "test-deployment-forced"), + jq.Match(`.spec.replicas == 3`), + )) +} diff --git a/pkg/controller/actions/action_test.go b/pkg/controller/actions/action_test.go index b5ec76056c3..a20cc9263c3 100644 --- a/pkg/controller/actions/action_test.go +++ b/pkg/controller/actions/action_test.go @@ -13,7 +13,7 @@ import ( "github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types" ) -func NewFakeClient(objs ...ctrlClient.Object) ctrlClient.WithWatch { //nolint:ireturn +func NewFakeClient(objs ...ctrlClient.Object) ctrlClient.WithWatch { scheme := runtime.NewScheme() utilruntime.Must(corev1.AddToScheme(scheme)) utilruntime.Must(appsv1.AddToScheme(scheme)) diff --git a/pkg/controller/actions/action_update_status.go b/pkg/controller/actions/action_update_status.go index 721f52e95d6..ca71ab7ed50 100644 --- a/pkg/controller/actions/action_update_status.go +++ b/pkg/controller/actions/action_update_status.go @@ -5,6 +5,7 @@ import ( "fmt" appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -70,11 +71,15 @@ func (a *UpdateStatusAction) Execute(ctx context.Context, rr *types.Reconciliati } } + s := obj.GetStatus() + s.ObservedGeneration = obj.GetGeneration() + conditionReady := metav1.Condition{ - Type: status.ConditionTypeReady, - Status: metav1.ConditionTrue, - Reason: ReadyReason, - Message: fmt.Sprintf("%d/%d deployments ready", ready, len(deployments.Items)), + Type: status.ConditionTypeReady, + Status: metav1.ConditionTrue, + Reason: ReadyReason, + Message: fmt.Sprintf("%d/%d deployments ready", ready, len(deployments.Items)), + ObservedGeneration: s.ObservedGeneration, } if len(deployments.Items) > 0 && ready != len(deployments.Items) { @@ -82,7 +87,7 @@ func (a *UpdateStatusAction) Execute(ctx context.Context, rr *types.Reconciliati conditionReady.Reason = DeploymentsNotReadyReason } - status.SetStatusCondition(obj, conditionReady) + meta.SetStatusCondition(&s.Conditions, conditionReady) return nil } diff --git a/pkg/controller/reconciler/component_reconciler.go b/pkg/controller/reconciler/component_reconciler.go index 202376041d7..0959eada835 100644 --- a/pkg/controller/reconciler/component_reconciler.go +++ b/pkg/controller/reconciler/component_reconciler.go @@ -101,7 +101,7 @@ func (r *ComponentReconciler[T]) Reconcile(ctx context.Context, req ctrl.Request DSC: &dscl.Items[0], DSCI: &dscil.Items[0], Platform: r.Platform, - Manifests: make(map[cluster.Platform]string), + Manifests: make([]types.ManifestInfo, 0), } // Handle deletion diff --git a/pkg/controller/types/types.go b/pkg/controller/types/types.go index a519632dce3..5018302b1b8 100644 --- a/pkg/controller/types/types.go +++ b/pkg/controller/types/types.go @@ -1,13 +1,17 @@ package types import ( + "path" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/opendatahub-io/opendatahub-operator/v2/apis/components" dscv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/datasciencecluster/v1" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/manifests/kustomize" ) type ResourceObject interface { @@ -19,11 +23,33 @@ type WithLogger interface { GetLogger() logr.Logger } +type ManifestInfo struct { + Path string + ContextDir string + SourcePath string + RenderOpts []kustomize.RenderOptsFn +} + +func (mi *ManifestInfo) ManifestsPath() string { + result := mi.Path + + if mi.ContextDir == "" { + result = path.Join(result, mi.ContextDir) + } + + if mi.SourcePath == "" { + result = path.Join(result, mi.SourcePath) + } + + return result +} + type ReconciliationRequest struct { client.Client Instance client.Object DSC *dscv1.DataScienceCluster DSCI *dsciv1.DSCInitialization Platform cluster.Platform - Manifests map[cluster.Platform]string + Manifests []ManifestInfo + Resources []unstructured.Unstructured } diff --git a/pkg/manifests/kustomize/kustomize.go b/pkg/manifests/kustomize/kustomize.go new file mode 100644 index 00000000000..3c217a73b7a --- /dev/null +++ b/pkg/manifests/kustomize/kustomize.go @@ -0,0 +1,28 @@ +package kustomize + +import ( + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +const ( + DefaultKustomizationFileName = "kustomization.yaml" + DefaultKustomizationFilePath = "default" +) + +func NewEngine(opts ...EngineOptsFn) *Engine { + e := Engine{ + k: krusty.MakeKustomizer(krusty.MakeDefaultOptions()), + fs: filesys.MakeFsOnDisk(), + renderOpts: renderOpts{ + kustomizationFileName: DefaultKustomizationFileName, + kustomizationFileOverlay: DefaultKustomizationFilePath, + }, + } + + for _, fn := range opts { + fn(&e) + } + + return &e +} diff --git a/pkg/manifests/kustomize/kustomize_engine.go b/pkg/manifests/kustomize/kustomize_engine.go new file mode 100644 index 00000000000..d8ad11c3409 --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_engine.go @@ -0,0 +1,82 @@ +package kustomize + +import ( + "fmt" + "path/filepath" + + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/kyaml/filesys" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/plugins" +) + +type Engine struct { + k *krusty.Kustomizer + fs filesys.FileSystem + renderOpts renderOpts +} + +func (e *Engine) Render(path string, opts ...RenderOptsFn) ([]unstructured.Unstructured, error) { + // poor man clone + ro := e.renderOpts + ro.labels = maps.Clone(e.renderOpts.labels) + ro.annotations = maps.Clone(e.renderOpts.annotations) + ro.plugins = slices.Clone(e.renderOpts.plugins) + + for _, fn := range opts { + fn(&ro) + } + + if !e.fs.Exists(filepath.Join(path, ro.kustomizationFileName)) { + path = filepath.Join(path, ro.kustomizationFileOverlay) + } + + resMap, err := e.k.Run(e.fs, path) + if err != nil { + return nil, err + } + + if ro.ns != "" { + plugin := plugins.CreateNamespaceApplierPlugin(ro.ns) + if err := plugin.Transform(resMap); err != nil { + return nil, fmt.Errorf("failed applying namespace plugin when preparing Kustomize resources. %w", err) + } + } + + if len(ro.labels) != 0 { + plugin := plugins.CreateSetLabelsPlugin(ro.labels) + if err := plugin.Transform(resMap); err != nil { + return nil, fmt.Errorf("failed applying labels plugin when preparing Kustomize resources. %w", err) + } + } + + if len(ro.annotations) != 0 { + plugin := plugins.CreateAddAnnotationsPlugin(ro.annotations) + if err := plugin.Transform(resMap); err != nil { + return nil, fmt.Errorf("failed applying annotations plugin when preparing Kustomize resources. %w", err) + } + } + + for i := range ro.plugins { + if err := ro.plugins[i].Transform(resMap); err != nil { + return nil, fmt.Errorf("failed applying %v plugin when preparing Kustomize resources. %w", ro.plugins[i], err) + } + } + + resources := resMap.Resources() + resp := make([]unstructured.Unstructured, len(resources)) + + for i := range resources { + m, err := resources[i].Map() + if err != nil { + return nil, fmt.Errorf("failed to transform Resources to Unstructured. %w", err) + } + + resp[i] = unstructured.Unstructured{Object: m} + } + + return resp, nil +} diff --git a/pkg/manifests/kustomize/kustomize_filters.go b/pkg/manifests/kustomize/kustomize_filters.go new file mode 100644 index 00000000000..d88feddf30d --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_filters.go @@ -0,0 +1,28 @@ +package kustomize + +import ( + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/kyaml/kio" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var _ resmap.Transformer = &filterPlugin{} +var _ kio.Filter = &filterProxy{} + +type filterPlugin struct { + f FilterFn +} + +func (p *filterPlugin) Transform(m resmap.ResMap) error { + return m.ApplyFilter(&filterProxy{ + f: p.f, + }) +} + +type filterProxy struct { + f FilterFn +} + +func (f *filterProxy) Filter(nodes []*kyaml.RNode) ([]*kyaml.RNode, error) { + return f.f(nodes) +} diff --git a/pkg/manifests/kustomize/kustomize_opts.go b/pkg/manifests/kustomize/kustomize_opts.go new file mode 100644 index 00000000000..8e5718444e4 --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_opts.go @@ -0,0 +1,20 @@ +package kustomize + +import ( + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type EngineOptsFn func(engine *Engine) + +func WithEngineFS(value filesys.FileSystem) EngineOptsFn { + return func(engine *Engine) { + engine.fs = value + } +} +func WithEngineRenderOpts(values ...RenderOptsFn) EngineOptsFn { + return func(engine *Engine) { + for _, fn := range values { + fn(&engine.renderOpts) + } + } +} diff --git a/pkg/manifests/kustomize/kustomize_render_opts.go b/pkg/manifests/kustomize/kustomize_render_opts.go new file mode 100644 index 00000000000..970365e121c --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_render_opts.go @@ -0,0 +1,101 @@ +package kustomize + +import ( + "sigs.k8s.io/kustomize/api/resmap" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" +) + +type FilterFn func(nodes []*kyaml.RNode) ([]*kyaml.RNode, error) + +type renderOpts struct { + kustomizationFileName string + kustomizationFileOverlay string + ns string + labels map[string]string + annotations map[string]string + plugins []resmap.Transformer +} + +type RenderOptsFn func(*renderOpts) + +func WithKustomizationFileName(value string) RenderOptsFn { + return func(opts *renderOpts) { + opts.kustomizationFileName = value + } +} + +func WithKustomizationOverlayPath(value string) RenderOptsFn { + return func(opts *renderOpts) { + opts.kustomizationFileOverlay = value + } +} + +func WithNamespace(value string) RenderOptsFn { + return func(opts *renderOpts) { + opts.ns = value + } +} + +func WithLabel(name string, value string) RenderOptsFn { + return func(opts *renderOpts) { + if opts.labels == nil { + opts.labels = map[string]string{} + } + + opts.labels[name] = value + } +} + +func WithLabels(values map[string]string) RenderOptsFn { + return func(opts *renderOpts) { + if opts.labels == nil { + opts.labels = map[string]string{} + } + + for k, v := range values { + opts.labels[k] = v + } + } +} + +func WithAnnotation(name string, value string) RenderOptsFn { + return func(opts *renderOpts) { + if opts.annotations == nil { + opts.annotations = map[string]string{} + } + + opts.annotations[name] = value + } +} + +func WithAnnotations(values map[string]string) RenderOptsFn { + return func(opts *renderOpts) { + if opts.annotations == nil { + opts.annotations = map[string]string{} + } + + for k, v := range values { + opts.annotations[k] = v + } + } +} + +func WithPlugin(value resmap.Transformer) RenderOptsFn { + return func(opts *renderOpts) { + opts.plugins = append(opts.plugins, value) + } +} + +func WithFilter(value FilterFn) RenderOptsFn { + return func(opts *renderOpts) { + opts.plugins = append(opts.plugins, &filterPlugin{f: value}) + } +} + +func WithFilters(values ...FilterFn) RenderOptsFn { + return func(opts *renderOpts) { + for i := range values { + opts.plugins = append(opts.plugins, &filterPlugin{f: values[i]}) + } + } +} diff --git a/pkg/manifests/kustomize/kustomize_support.go b/pkg/manifests/kustomize/kustomize_support.go new file mode 100644 index 00000000000..bb11f436938 --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_support.go @@ -0,0 +1,16 @@ +package kustomize + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func NodeToUnstructured(n *kyaml.RNode) unstructured.Unstructured { + u := unstructured.Unstructured{} + u.SetAPIVersion(n.GetApiVersion()) + u.SetKind(n.GetKind()) + u.SetNamespace(n.GetNamespace()) + u.SetName(n.GetName()) + + return u +} diff --git a/pkg/manifests/kustomize/kustomize_test.go b/pkg/manifests/kustomize/kustomize_test.go new file mode 100644 index 00000000000..c1bf663fb89 --- /dev/null +++ b/pkg/manifests/kustomize/kustomize_test.go @@ -0,0 +1,68 @@ +package kustomize_test + +import ( + "path" + "testing" + + "github.com/rs/xid" + "sigs.k8s.io/kustomize/kyaml/filesys" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/manifests/kustomize" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/jq" + + . "github.com/onsi/gomega" +) + +const testEngineKustomization = ` +apiVersion: kustomize.config.k8s.io/v1beta1 +resources: +- test-engine-cm.yaml +` + +const testEngineConfigMap = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-engine-cm +data: + foo: bar +` + +func TestEngine(t *testing.T) { + g := NewWithT(t) + id := xid.New().String() + ns := xid.New().String() + fs := filesys.MakeFsInMemory() + + e := kustomize.NewEngine( + kustomize.WithEngineFS(fs), + ) + + _ = fs.MkdirAll(path.Join(id, kustomize.DefaultKustomizationFilePath)) + _ = fs.WriteFile(path.Join(id, kustomize.DefaultKustomizationFileName), []byte(testEngineKustomization)) + _ = fs.WriteFile(path.Join(id, "test-engine-cm.yaml"), []byte(testEngineConfigMap)) + + r, err := e.Render( + id, + kustomize.WithNamespace(ns), + kustomize.WithLabel("component.opendatahub.io/name", "foo"), + kustomize.WithLabel("platform.opendatahub.io/namespace", ns), + kustomize.WithAnnotations(map[string]string{ + "platform.opendatahub.io/release": "1.2.3", + "platform.opendatahub.io/type": "managed", + }), + ) + + g.Expect(err).NotTo(HaveOccurred()) + + g.Expect(r).Should(And( + HaveLen(1), + HaveEach(And( + jq.Match(`.metadata.namespace == "%s"`, ns), + jq.Match(`.metadata.labels."component.opendatahub.io/name" == "%s"`, "foo"), + jq.Match(`.metadata.labels."platform.opendatahub.io/namespace" == "%s"`, ns), + jq.Match(`.metadata.annotations."platform.opendatahub.io/release" == "%s"`, "1.2.3"), + jq.Match(`.metadata.annotations."platform.opendatahub.io/type" == "%s"`, "managed"), + )), + )) +} diff --git a/pkg/plugins/addAnnotationsplugin.go b/pkg/plugins/addAnnotationsplugin.go new file mode 100644 index 00000000000..1e0fbbaeb59 --- /dev/null +++ b/pkg/plugins/addAnnotationsplugin.go @@ -0,0 +1,20 @@ +package plugins + +import ( + "sigs.k8s.io/kustomize/api/builtins" //nolint:staticcheck // Remove after package update + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/resid" +) + +func CreateAddAnnotationsPlugin(annotations map[string]string) *builtins.AnnotationsTransformerPlugin { + return &builtins.AnnotationsTransformerPlugin{ + Annotations: annotations, + FieldSpecs: []types.FieldSpec{ + { + Gvk: resid.Gvk{}, + Path: "metadata/annotations", + CreateIfNotPresent: true, + }, + }, + } +} diff --git a/pkg/plugins/removerplugin.go b/pkg/plugins/removerplugin.go index e0c852f7c69..330aea788d4 100644 --- a/pkg/plugins/removerplugin.go +++ b/pkg/plugins/removerplugin.go @@ -73,9 +73,18 @@ func (f RemoverFilter) run(node *kyaml.RNode) (*kyaml.RNode, error) { return node, errors.New("no field set to remove, path to the field cannot be empty") } + return ClearFieldFor(node, f.Gvk, f.Path) +} + +func ClearFieldFor(node *kyaml.RNode, gvk schema.GroupVersionKind, fieldPath []string) (*kyaml.RNode, error) { + pathLen := len(fieldPath) + if pathLen == 0 { + return node, nil + } + typeMeta := kyaml.TypeMeta{ - APIVersion: f.Gvk.GroupVersion().String(), - Kind: f.Gvk.Kind, + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, } meta, err := node.GetMeta() @@ -87,8 +96,29 @@ func (f RemoverFilter) run(node *kyaml.RNode) (*kyaml.RNode, error) { return node, nil } - path := f.Path[:pathLen-1] - name := f.Path[pathLen-1] + path := fieldPath[:pathLen-1] + name := fieldPath[pathLen-1] + + matcher := &kyaml.PathMatcher{Path: path} + result, err := node.Pipe(matcher) + if err != nil { + return node, err + } + + return node, result.VisitElements( + func(node *kyaml.RNode) error { + return node.PipeE(kyaml.FieldClearer{Name: name}) + }) +} + +func ClearField(node *kyaml.RNode, fieldPath []string) (*kyaml.RNode, error) { + pathLen := len(fieldPath) + if pathLen == 0 { + return node, nil + } + + path := fieldPath[:pathLen-1] + name := fieldPath[pathLen-1] matcher := &kyaml.PathMatcher{Path: path} result, err := node.Pipe(matcher) diff --git a/pkg/utils/test/matchers/jq/jq_matcher.go b/pkg/utils/test/matchers/jq/jq_matcher.go new file mode 100644 index 00000000000..ab0aef9b3fc --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_matcher.go @@ -0,0 +1,60 @@ +package jq + +import ( + "fmt" + + "github.com/itchyny/gojq" + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/types" +) + +//nolint:ireturn +func Match(format string, args ...any) types.GomegaMatcher { + return &jqMatcher{ + Expression: fmt.Sprintf(format, args...), + } +} + +var _ types.GomegaMatcher = &jqMatcher{} + +type jqMatcher struct { + Expression string + firstFailurePath []interface{} +} + +func (matcher *jqMatcher) Match(actual interface{}) (bool, error) { + query, err := gojq.Parse(matcher.Expression) + if err != nil { + return false, fmt.Errorf("unable to parse expression %s, %w", matcher.Expression, err) + } + + data, err := toType(actual) + if err != nil { + return false, err + } + + it := query.Run(data) + + v, ok := it.Next() + if !ok { + return false, nil + } + + if err, ok := v.(error); ok { + return false, err + } + + if match, ok := v.(bool); ok { + return match, nil + } + + return false, nil +} + +func (matcher *jqMatcher) FailureMessage(actual interface{}) string { + return formattedMessage(format.Message(fmt.Sprintf("%v", actual), "to match expression", matcher.Expression), matcher.firstFailurePath) +} + +func (matcher *jqMatcher) NegatedFailureMessage(actual interface{}) string { + return formattedMessage(format.Message(fmt.Sprintf("%v", actual), "not to match expression", matcher.Expression), matcher.firstFailurePath) +} diff --git a/pkg/utils/test/matchers/jq/jq_matcher_test.go b/pkg/utils/test/matchers/jq/jq_matcher_test.go new file mode 100644 index 00000000000..73cbe03280c --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_matcher_test.go @@ -0,0 +1,89 @@ +package jq_test + +import ( + "encoding/json" + "testing" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/jq" + + . "github.com/onsi/gomega" +) + +func TestMatcher(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(`{"a":1}`).Should( + jq.Match(`.a == 1`), + ) + + g.Expect(`{"a":1}`).Should( + Not( + jq.Match(`.a == 2`), + ), + ) + + g.Expect(`{"Values":[ "foo" ]}`).Should( + jq.Match(`.Values | if . then any(. == "foo") else false end`), + ) + + g.Expect(`{"Values":[ "foo" ]}`).Should( + Not( + jq.Match(`.Values | if . then any(. == "bar") else false end`), + ), + ) + + g.Expect(`{"Values": null}`).Should( + Not( + jq.Match(`.Values | if . then any(. == "foo") else false end`), + ), + ) + + g.Expect(`{ "status": { "foo": { "bar": "fr", "baz": "fb" } } }`).Should( + And( + jq.Match(`.status.foo.bar == "fr"`), + jq.Match(`.status.foo.baz == "fb"`), + ), + ) +} + +func TestMatcherWithType(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(map[string]any{"a": 1}). + Should( + WithTransform(json.Marshal, jq.Match(`.a == 1`)), + ) + + g.Expect( + map[string]any{ + "status": map[string]any{ + "foo": map[string]any{ + "bar": "fr", + "baz": "fb", + }, + }, + }). + Should( + WithTransform(json.Marshal, And( + jq.Match(`.status.foo.bar == "fr"`), + jq.Match(`.status.foo.baz == "fb"`), + )), + ) + + g.Expect(map[string]any{"a": 1}). + Should(jq.Match(`.a == 1`)) + + g.Expect( + struct { + A int `json:"a"` + }{ + A: 1, + }). + Should( + WithTransform(json.Marshal, jq.Match(`.a == 1`)), + ) +} diff --git a/pkg/utils/test/matchers/jq/jq_support.go b/pkg/utils/test/matchers/jq/jq_support.go new file mode 100644 index 00000000000..3916cb9bac5 --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_support.go @@ -0,0 +1,129 @@ +package jq + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strings" + + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/gbytes" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func formattedMessage(comparisonMessage string, failurePath []interface{}) string { + diffMessage := "" + + if len(failurePath) != 0 { + diffMessage = "\n\nfirst mismatched key: " + formattedFailurePath(failurePath) + } + + return comparisonMessage + diffMessage +} + +func formattedFailurePath(failurePath []interface{}) string { + formattedPaths := make([]string, 0) + + for i := len(failurePath) - 1; i >= 0; i-- { + switch p := failurePath[i].(type) { + case int: + val := fmt.Sprintf(`[%d]`, p) + formattedPaths = append(formattedPaths, val) + default: + if i != len(failurePath)-1 { + formattedPaths = append(formattedPaths, ".") + } + + val := fmt.Sprintf(`"%s"`, p) + formattedPaths = append(formattedPaths, val) + } + } + + return strings.Join(formattedPaths, "") +} + +//nolint:cyclop +func toType(in any) (any, error) { + switch v := in.(type) { + case string: + d, err := byteToType([]byte(v)) + if err != nil { + return nil, err + } + + return d, nil + case []byte: + d, err := byteToType(v) + if err != nil { + return nil, err + } + + return d, nil + case json.RawMessage: + d, err := byteToType(v) + if err != nil { + return nil, err + } + + return d, nil + case *gbytes.Buffer: + d, err := byteToType(v.Contents()) + if err != nil { + return nil, err + } + + return d, nil + case io.Reader: + data, err := io.ReadAll(v) + if err != nil { + return nil, fmt.Errorf("failed to read from reader: %w", err) + } + + d, err := byteToType(data) + if err != nil { + return nil, err + } + + return d, nil + case unstructured.Unstructured: + return v.Object, nil + case *unstructured.Unstructured: + return v.Object, nil + } + + switch reflect.TypeOf(in).Kind() { + case reflect.Map: + return in, nil + case reflect.Slice: + return in, nil + default: + return nil, fmt.Errorf("unsuported type:\n%s", format.Object(in, 1)) + } +} + +func byteToType(in []byte) (any, error) { + if len(in) == 0 { + return nil, errors.New("a valid Json document is expected") + } + + switch in[0] { + case '{': + data := make(map[string]any) + if err := json.Unmarshal(in, &data); err != nil { + return nil, fmt.Errorf("unable to unmarshal result, %w", err) + } + + return data, nil + case '[': + var data []any + if err := json.Unmarshal(in, &data); err != nil { + return nil, fmt.Errorf("unable to unmarshal result, %w", err) + } + + return data, nil + default: + return nil, errors.New("a Json Array or Object is required") + } +} diff --git a/pkg/utils/test/matchers/jq/jq_support_test.go b/pkg/utils/test/matchers/jq/jq_support_test.go new file mode 100644 index 00000000000..c314775089a --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_support_test.go @@ -0,0 +1,54 @@ +//nolint:testpackage +package jq + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/onsi/gomega/gbytes" + + . "github.com/onsi/gomega" +) + +func TestToType(t *testing.T) { + t.Parallel() + + typeTestData := []byte(`{ "foo": "bar" }`) + g := NewWithT(t) + + items := map[string]func() any{ + "gbytes": func() any { + b := gbytes.NewBuffer() + + _, err := b.Write(typeTestData) + g.Expect(err).ShouldNot(HaveOccurred()) + + return b + }, + "bytes": func() any { + return typeTestData + }, + "string": func() any { + return string(typeTestData) + }, + "raw-message": func() any { + return json.RawMessage(typeTestData) + }, + } + + for name, fn := range items { + f := fn + + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt, err := toType(f()) + + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(tt).Should(Satisfy(func(in any) bool { + return reflect.TypeOf(in).Kind() == reflect.Map + })) + }) + } +} diff --git a/pkg/utils/test/matchers/jq/jq_transform.go b/pkg/utils/test/matchers/jq/jq_transform.go new file mode 100644 index 00000000000..cd477ec6feb --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_transform.go @@ -0,0 +1,34 @@ +package jq + +import ( + "fmt" + + "github.com/itchyny/gojq" +) + +func Extract(expression string) func(in any) (any, error) { + return func(in any) (any, error) { + query, err := gojq.Parse(expression) + if err != nil { + return nil, fmt.Errorf("unable to parse expression %s, %w", expression, err) + } + + data, err := toType(in) + if err != nil { + return false, err + } + + it := query.Run(data) + + v, ok := it.Next() + if !ok { + return false, nil + } + + if err, ok := v.(error); ok { + return false, err + } + + return v, nil + } +} diff --git a/pkg/utils/test/matchers/jq/jq_transform_test.go b/pkg/utils/test/matchers/jq/jq_transform_test.go new file mode 100644 index 00000000000..9e5cf8d6889 --- /dev/null +++ b/pkg/utils/test/matchers/jq/jq_transform_test.go @@ -0,0 +1,31 @@ +package jq_test + +import ( + "encoding/json" + "testing" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/jq" + + . "github.com/onsi/gomega" +) + +func TestExtract(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(`{ "foo": { "a": 1 }}`).Should( + WithTransform(jq.Extract(`.foo`), WithTransform(json.Marshal, + jq.Match(`.a == 1`), + )), + ) + + g.Expect(`{ "status": { "foo": { "bar": "fr", "baz": "fz" } } }`).Should( + WithTransform(jq.Extract(`.status`), + And( + jq.Match(`.foo.bar == "fr"`), + jq.Match(`.foo.baz == "fz"`), + ), + ), + ) +} diff --git a/pkg/utils/test/matchers/yq/yq.go b/pkg/utils/test/matchers/yq/yq.go new file mode 100644 index 00000000000..e471e700827 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq.go @@ -0,0 +1,12 @@ +package yq + +import ( + "github.com/mikefarah/yq/v4/pkg/yqlib" + "gopkg.in/op/go-logging.v1" +) + +//nolint:gochecknoinits +func init() { + logging.SetBackend(&nullLogger{}) + yqlib.InitExpressionParser() +} diff --git a/pkg/utils/test/matchers/yq/yq_matcher.go b/pkg/utils/test/matchers/yq/yq_matcher.go new file mode 100644 index 00000000000..8471b199fa3 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_matcher.go @@ -0,0 +1,60 @@ +package yq + +import ( + "errors" + "fmt" + "strconv" + + "github.com/mikefarah/yq/v4/pkg/yqlib" + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/types" +) + +//nolint:ireturn +func Match(format string, args ...any) types.GomegaMatcher { + return &yqMatcher{ + Expression: fmt.Sprintf(format, args...), + } +} + +var _ types.GomegaMatcher = &yqMatcher{} + +type yqMatcher struct { + Expression string + firstFailurePath []interface{} +} + +func (matcher *yqMatcher) Match(actual interface{}) (bool, error) { + results, err := evaluate(matcher.Expression, actual) + if err != nil { + return false, err + } + + if results == nil { + return false, nil + } + + if results.Len() != 1 { + return false, errors.New("TODO_1") + } + + n, ok := results.Front().Value.(*yqlib.CandidateNode) + if !ok { + return false, errors.New("TODO_2") + } + + match, err := strconv.ParseBool(n.Value) + if err != nil { + return false, fmt.Errorf("failure parsing result: %w", err) + } + + return match, nil +} + +func (matcher *yqMatcher) FailureMessage(actual interface{}) string { + return formattedMessage(format.Message(fmt.Sprintf("%v", actual), "to match expression", matcher.Expression), matcher.firstFailurePath) +} + +func (matcher *yqMatcher) NegatedFailureMessage(actual interface{}) string { + return formattedMessage(format.Message(fmt.Sprintf("%v", actual), "not to match expression", matcher.Expression), matcher.firstFailurePath) +} diff --git a/pkg/utils/test/matchers/yq/yq_matcher_test.go b/pkg/utils/test/matchers/yq/yq_matcher_test.go new file mode 100644 index 00000000000..a6a48d2c227 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_matcher_test.go @@ -0,0 +1,62 @@ +package yq_test + +import ( + "testing" + + "github.com/goccy/go-yaml" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/yq" + + . "github.com/onsi/gomega" +) + +func TestMatcher(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(`a: 1`).Should( + yq.Match(`.a == 1`), + ) + g.Expect(`a: 1`).Should( + Not( + yq.Match(`.a == 2`), + ), + ) +} + +func TestMatcherWithType(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(map[string]any{"a": 1}).Should( + WithTransform(yaml.Marshal, yq.Match(`.a == 1`)), + ) + + g.Expect( + map[string]any{ + "status": map[string]any{ + "foo": map[string]any{ + "bar": "fr", + "baz": "fb", + }, + }, + }). + Should( + WithTransform(yaml.Marshal, And( + yq.Match(`.status.foo.bar == "fr"`), + yq.Match(`.status.foo.baz == "fb"`), + )), + ) + + g.Expect( + struct { + A int `yaml:"a"` + }{ + A: 1, + }). + Should( + WithTransform(yaml.Marshal, yq.Match(`.a == 1`)), + ) +} diff --git a/pkg/utils/test/matchers/yq/yq_support.go b/pkg/utils/test/matchers/yq/yq_support.go new file mode 100644 index 00000000000..ab69be84628 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_support.go @@ -0,0 +1,106 @@ +package yq + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "strings" + + "github.com/goccy/go-yaml" + "github.com/mikefarah/yq/v4/pkg/yqlib" + "github.com/onsi/gomega/format" +) + +const ( + defaultIndent = 2 +) + +func formattedMessage(comparisonMessage string, failurePath []interface{}) string { + diffMessage := "" + + if len(failurePath) != 0 { + diffMessage = "\n\nfirst mismatched key: " + formattedFailurePath(failurePath) + } + + return comparisonMessage + diffMessage +} + +func formattedFailurePath(failurePath []interface{}) string { + formattedPaths := make([]string, 0) + + for i := len(failurePath) - 1; i >= 0; i-- { + switch p := failurePath[i].(type) { + case int: + val := fmt.Sprintf(`[%d]`, p) + formattedPaths = append(formattedPaths, val) + default: + if i != len(failurePath)-1 { + formattedPaths = append(formattedPaths, ".") + } + + val := fmt.Sprintf(`"%s"`, p) + formattedPaths = append(formattedPaths, val) + } + } + + return strings.Join(formattedPaths, "") +} + +func toString(in any) (string, error) { + switch v := in.(type) { + case string: + return v, nil + case []byte: + return string(v), nil + case yaml.BytesMarshaler: + r, err := v.MarshalYAML() + if err != nil { + return "", fmt.Errorf("unablt to unmarshal type: %w", err) + } + + return string(r), nil + default: + return "", fmt.Errorf("unsupported type:\n%s", format.Object(in, 1)) + } +} + +func evaluate(expression string, actual interface{}) (*list.List, error) { + data, err := toString(actual) + if err != nil { + return nil, err + } + + documents, err := readDocuments([]byte(data)) + if err != nil { + return nil, err + } + + results, err := yqlib.NewAllAtOnceEvaluator().EvaluateCandidateNodes(expression, documents) + if err != nil { + return nil, fmt.Errorf("failure evaluating expression: %w", err) + } + + return results, nil +} + +func readDocuments(data []byte) (*list.List, error) { + br := bytes.NewReader(data) + reader := bufio.NewReader(br) + + decoder := yqlib.NewYamlDecoder(yqlib.YamlPreferences{ + Indent: defaultIndent, + ColorsEnabled: false, + LeadingContentPreProcessing: true, + PrintDocSeparators: true, + UnwrapScalar: true, + EvaluateTogether: false, + }) + + documents, err := yqlib.ReadDocuments(reader, decoder) + if err != nil { + return nil, fmt.Errorf("failure reading document: %w", err) + } + + return documents, nil +} diff --git a/pkg/utils/test/matchers/yq/yq_support_logging.go b/pkg/utils/test/matchers/yq/yq_support_logging.go new file mode 100644 index 00000000000..07eb64be5b1 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_support_logging.go @@ -0,0 +1,13 @@ +package yq + +import ( + "gopkg.in/op/go-logging.v1" +) + +type nullLogger struct { + // disable logging for yq +} + +func (l *nullLogger) Log(_ logging.Level, _ int, _ *logging.Record) error { + return nil +} diff --git a/pkg/utils/test/matchers/yq/yq_transform.go b/pkg/utils/test/matchers/yq/yq_transform.go new file mode 100644 index 00000000000..e07a55cf82e --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_transform.go @@ -0,0 +1,35 @@ +package yq + +import ( + "bytes" + "fmt" + + "github.com/mikefarah/yq/v4/pkg/yqlib" +) + +func Extract(expression string) func(in any) (any, error) { + return func(in any) (any, error) { + results, err := evaluate(expression, in) + if err != nil { + return false, err + } + + out := new(bytes.Buffer) + + encoder := yqlib.NewYamlEncoder(yqlib.YamlPreferences{ + Indent: defaultIndent, + ColorsEnabled: false, + LeadingContentPreProcessing: true, + PrintDocSeparators: true, + UnwrapScalar: true, + EvaluateTogether: false, + }) + + printer := yqlib.NewPrinter(encoder, yqlib.NewSinglePrinterWriter(out)) + if err := printer.PrintResults(results); err != nil { + return "", fmt.Errorf("failure rendering results: %w", err) + } + + return out.String(), nil + } +} diff --git a/pkg/utils/test/matchers/yq/yq_transform_test.go b/pkg/utils/test/matchers/yq/yq_transform_test.go new file mode 100644 index 00000000000..948bd270032 --- /dev/null +++ b/pkg/utils/test/matchers/yq/yq_transform_test.go @@ -0,0 +1,40 @@ +package yq_test + +import ( + "testing" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/utils/test/matchers/yq" + + . "github.com/onsi/gomega" +) + +const e1 = ` +foo: + a: 1 +` + +const e2 = ` +status: + foo: + bar: fr + baz: fz +` + +func TestExtract(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + g.Expect(e1).Should( + WithTransform(yq.Extract(`.foo`), yq.Match(`.a == 1`)), + ) + + g.Expect(e2).Should( + WithTransform(yq.Extract(`.status`), + And( + yq.Match(`.foo.bar == "fr"`), + yq.Match(`.foo.baz == "fz"`), + ), + ), + ) +}