diff --git a/bundle/manifests/odf-multicluster-orchestrator.clusterserviceversion.yaml b/bundle/manifests/odf-multicluster-orchestrator.clusterserviceversion.yaml index 6e2b830c..8e5746e2 100644 --- a/bundle/manifests/odf-multicluster-orchestrator.clusterserviceversion.yaml +++ b/bundle/manifests/odf-multicluster-orchestrator.clusterserviceversion.yaml @@ -36,7 +36,7 @@ metadata: ] capabilities: Basic Install console.openshift.io/plugins: '["odf-multicluster-console"]' - createdAt: "2024-04-03T11:58:23Z" + createdAt: "2024-07-12T13:14:27Z" olm.skipRange: "" operators.openshift.io/infrastructure-features: '["disconnected"]' operators.operatorframework.io/builder: operator-sdk-v1.34.1 @@ -224,6 +224,16 @@ spec: - roles verbs: - '*' + - apiGroups: + - view.open-cluster-management.io + resources: + - managedclusterviews + verbs: + - create + - get + - list + - update + - watch - apiGroups: - work.open-cluster-management.io resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 12794311..efee82c3 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -161,6 +161,16 @@ rules: - roles verbs: - '*' +- apiGroups: + - view.open-cluster-management.io + resources: + - managedclusterviews + verbs: + - create + - get + - list + - update + - watch - apiGroups: - work.open-cluster-management.io resources: diff --git a/controllers/managedcluster_controller.go b/controllers/managedcluster_controller.go new file mode 100644 index 00000000..d7589f16 --- /dev/null +++ b/controllers/managedcluster_controller.go @@ -0,0 +1,137 @@ +package controllers + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils" + viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + clusterv1 "open-cluster-management.io/api/cluster/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type ManagedClusterReconciler struct { + Client client.Client + Logger *slog.Logger +} + +const ( + OdfInfoClusterClaimNamespacedName = "odfinfo.odf.openshift.io" +) + +// +kubebuilder:rbac:groups=view.open-cluster-management.io,resources=managedclusterviews,verbs=get;list;watch;create;update +func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + logger := r.Logger.With("ManagedCluster", req.NamespacedName) + logger.Info("Reconciling ManagedCluster") + + var managedCluster clusterv1.ManagedCluster + if err := r.Client.Get(ctx, req.NamespacedName, &managedCluster); err != nil { + if client.IgnoreNotFound(err) != nil { + logger.Error("Failed to get ManagedCluster", "error", err) + } + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := r.processManagedClusterViews(ctx, managedCluster); err != nil { + logger.Error("Failed to ensure ManagedClusterView", "error", err) + return ctrl.Result{}, err + } + + logger.Info("Successfully reconciled ManagedCluster", "name", managedCluster.Name) + + return ctrl.Result{}, nil +} + +func hasRequiredODFKey(mc *clusterv1.ManagedCluster) bool { + claims := mc.Status.ClusterClaims + for _, claim := range claims { + if claim.Name == OdfInfoClusterClaimNamespacedName { + return true + } + } + return false + +} +func (r *ManagedClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.Logger.Info("Setting up ManagedClusterReconciler with manager") + managedClusterPredicate := predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + obj, ok := e.ObjectNew.(*clusterv1.ManagedCluster) + if !ok { + return false + } + return hasRequiredODFKey(obj) + }, + CreateFunc: func(e event.CreateEvent) bool { + obj, ok := e.Object.(*clusterv1.ManagedCluster) + if !ok { + return false + } + return hasRequiredODFKey(obj) + }, + + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&clusterv1.ManagedCluster{}, builder.WithPredicates(managedClusterPredicate, predicate.GenerationChangedPredicate{})). + Owns(&viewv1beta1.ManagedClusterView{}). + Complete(r) +} + +func (r *ManagedClusterReconciler) processManagedClusterViews(ctx context.Context, managedCluster clusterv1.ManagedCluster) error { + resourceType := "ConfigMap" + odfInfoConfigMapNamespacedName, err := getNamespacedNameForClusterInfo(managedCluster) + if err != nil { + return fmt.Errorf("error while getting NamespacedName of the %s. %w", resourceType, err) + } + + enabled := true + disabled := false + mcvOwnerRef := &metav1.OwnerReference{ + APIVersion: managedCluster.APIVersion, + Kind: managedCluster.Kind, + UID: managedCluster.UID, + Name: managedCluster.Name, + Controller: &enabled, + BlockOwnerDeletion: &disabled, + } + + mcv, operationResult, err := utils.CreateOrUpdateManagedClusterView(ctx, r.Client, odfInfoConfigMapNamespacedName.Name, odfInfoConfigMapNamespacedName.Namespace, resourceType, managedCluster.Name, mcvOwnerRef) + if err != nil { + return fmt.Errorf("failed to create or update ManagedClusterView. %w", err) + + } + r.Logger.Info(fmt.Sprintf("ManagedClusterView was %s", operationResult), "ManagedClusterView", mcv.Name) + + return nil +} + +func getNamespacedNameForClusterInfo(managedCluster clusterv1.ManagedCluster) (types.NamespacedName, error) { + clusterClaims := managedCluster.Status.ClusterClaims + for _, claim := range clusterClaims { + if claim.Name == OdfInfoClusterClaimNamespacedName { + namespacedName := strings.Split(claim.Value, "/") + if len(namespacedName) != 2 { + return types.NamespacedName{}, fmt.Errorf("invalid format for namespaced name claim: expected 'namespace/name', got '%s'", claim.Value) + } + return types.NamespacedName{Namespace: namespacedName[0], Name: namespacedName[1]}, nil + } + } + + return types.NamespacedName{}, fmt.Errorf("cannot find %q in ManagedCluster status", OdfInfoClusterClaimNamespacedName) +} diff --git a/controllers/managedcluster_controller_test.go b/controllers/managedcluster_controller_test.go new file mode 100644 index 00000000..cfed12ee --- /dev/null +++ b/controllers/managedcluster_controller_test.go @@ -0,0 +1,249 @@ +//go:build unit +// +build unit + +package controllers + +import ( + "context" + "reflect" + "testing" + + "github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils" + viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clusterv1 "open-cluster-management.io/api/cluster/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestManagedClusterReconcile(t *testing.T) { + scheme := runtime.NewScheme() + _ = clusterv1.AddToScheme(scheme) + _ = viewv1beta1.AddToScheme(scheme) + + client := fake.NewClientBuilder().WithScheme(scheme).Build() + logger := utils.GetLogger(utils.GetZapLogger(true)) + + reconciler := &ManagedClusterReconciler{ + Client: client, + Logger: logger, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Name: "test-cluster"}, + } + + t.Run("ManagedCluster not found", func(t *testing.T) { + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + }) + + t.Run("ManagedCluster found", func(t *testing.T) { + managedCluster := clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "openshift-storage/odf-info", + }, + }, + }, + } + + _ = client.Create(context.TODO(), &managedCluster) + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + }) +} + +func TestProcessManagedClusterViews(t *testing.T) { + scheme := runtime.NewScheme() + _ = clusterv1.AddToScheme(scheme) + _ = viewv1beta1.AddToScheme(scheme) + + client := fake.NewClientBuilder().WithScheme(scheme).Build() + logger := utils.GetLogger(utils.GetZapLogger(true)) + + reconciler := &ManagedClusterReconciler{ + Client: client, + Logger: logger, + } + + t.Run("ManagedClusterView does not exist. It should create it", func(t *testing.T) { + managedCluster := clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "openshift-storage/odf-info", + }, + }, + }, + } + err := reconciler.processManagedClusterViews(context.TODO(), managedCluster) + assert.NoError(t, err) + + createdMCV := &viewv1beta1.ManagedClusterView{} + err = client.Get(context.TODO(), types.NamespacedName{Name: utils.GetManagedClusterViewName("test-cluster"), Namespace: "test-cluster"}, createdMCV) + assert.NoError(t, err) + assert.Equal(t, utils.GetManagedClusterViewName("test-cluster"), createdMCV.Name) + assert.Equal(t, "test-cluster", createdMCV.Namespace) + assert.Equal(t, "odf-info", createdMCV.Spec.Scope.Name) + assert.Equal(t, "openshift-storage", createdMCV.Spec.Scope.Namespace) + }) + + t.Run("ManagedClusterView exists but spec does not match. Desired spec should be reached", func(t *testing.T) { + existingMCV := &viewv1beta1.ManagedClusterView{ + ObjectMeta: metav1.ObjectMeta{ + Name: utils.GetManagedClusterViewName("test-cluster"), + Namespace: "test-cluster", + }, + Spec: viewv1beta1.ViewSpec{ + Scope: viewv1beta1.ViewScope{ + Name: "old-configmap", + Namespace: "old-namespace", + Resource: "ConfigMap", + }, + }, + } + _ = client.Create(context.TODO(), existingMCV) + + managedCluster := clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "openshift-storage/odf-info", + }, + }, + }, + } + + err := reconciler.processManagedClusterViews(context.TODO(), managedCluster) + assert.NoError(t, err) + + updatedMCV := &viewv1beta1.ManagedClusterView{} + _ = client.Get(context.TODO(), types.NamespacedName{Name: utils.GetManagedClusterViewName("test-cluster"), Namespace: "test-cluster"}, updatedMCV) + assert.Equal(t, "odf-info", updatedMCV.Spec.Scope.Name) + assert.Equal(t, "openshift-storage", updatedMCV.Spec.Scope.Namespace) + }) + + t.Run("ManagedClusterView exists and spec matches. Nothing should change. It should be error free", func(t *testing.T) { + existingMCV := &viewv1beta1.ManagedClusterView{ + ObjectMeta: metav1.ObjectMeta{ + Name: utils.GetManagedClusterViewName("test-cluster"), + Namespace: "test-cluster", + }, + Spec: viewv1beta1.ViewSpec{ + Scope: viewv1beta1.ViewScope{ + Name: "odf-info", + Namespace: "openshift-storage", + Resource: "ConfigMap", + }, + }, + } + _ = client.Create(context.TODO(), existingMCV) + + managedCluster := clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "openshift-storage/odf-info", + }, + }, + }, + } + err := reconciler.processManagedClusterViews(context.TODO(), managedCluster) + assert.NoError(t, err) + }) +} + +func Test_getNamespacedNameForClusterInfo(t *testing.T) { + type args struct { + managedCluster clusterv1.ManagedCluster + } + tests := []struct { + name string + args args + want types.NamespacedName + wantErr bool + }{ + { + name: "Valid Namespaced Name Claim", + args: args{ + managedCluster: clusterv1.ManagedCluster{ + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "namespace/name", + }, + }, + }, + }, + }, + want: types.NamespacedName{Namespace: "namespace", Name: "name"}, + wantErr: false, + }, + { + name: "Missing Namespaced Name Claim", + args: args{ + managedCluster: clusterv1.ManagedCluster{ + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{}, + }, + }, + }, + want: types.NamespacedName{}, + wantErr: true, + }, + { + name: "Invalid Format for Namespaced Name Claim", + args: args{ + managedCluster: clusterv1.ManagedCluster{ + Status: clusterv1.ManagedClusterStatus{ + ClusterClaims: []clusterv1.ManagedClusterClaim{ + { + Name: OdfInfoClusterClaimNamespacedName, + Value: "invalidformat", + }, + }, + }, + }, + }, + want: types.NamespacedName{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getNamespacedNameForClusterInfo(tt.args.managedCluster) + if (err != nil) != tt.wantErr { + t.Errorf("getNamespacedNameForClusterInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getNamespacedNameForClusterInfo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/controllers/manager.go b/controllers/manager.go index 3a9c99bb..455f48aa 100644 --- a/controllers/manager.go +++ b/controllers/manager.go @@ -13,6 +13,7 @@ import ( "github.com/red-hat-storage/odf-multicluster-orchestrator/console" "github.com/red-hat-storage/odf-multicluster-orchestrator/controllers/utils" "github.com/spf13/cobra" + viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -43,6 +44,7 @@ func init() { utilruntime.Must(ramenv1alpha1.AddToScheme(mgrScheme)) utilruntime.Must(workv1.AddToScheme(mgrScheme)) + utilruntime.Must(viewv1beta1.AddToScheme(mgrScheme)) //+kubebuilder:scaffold:scheme } @@ -140,6 +142,14 @@ func (o *ManagerOptions) runManager() { os.Exit(1) } + if err = (&ManagedClusterReconciler{ + Client: mgr.GetClient(), + Logger: logger.With("controller", "ManagedClusterReconciler"), + }).SetupWithManager(mgr); err != nil { + logger.Error("Failed to create ManagedCluster controller", "error", err) + os.Exit(1) + } + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { err = console.InitConsole(ctx, mgr.GetClient(), o.MulticlusterConsolePort, namespace) if err != nil { diff --git a/controllers/mirrorpeer_controller.go b/controllers/mirrorpeer_controller.go index aa5a1f5f..33cc2a9f 100644 --- a/controllers/mirrorpeer_controller.go +++ b/controllers/mirrorpeer_controller.go @@ -71,6 +71,7 @@ const spokeClusterRoleBindingName = "spoke-clusterrole-bindings" //+kubebuilder:rbac:groups=addon.open-cluster-management.io,resources=managedclusteraddons/status,verbs=* //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;create;update;watch //+kubebuilder:rbac:groups=console.openshift.io,resources=consoleplugins,verbs=get;list;create;update;watch +//+kubebuilder:rbac:groups=view.open-cluster-management.io,resources=managedclusterviews,verbs=get;list;watch;create;update //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;create;update;watch //+kubebuilder:rbac:groups=ramendr.openshift.io,resources=drclusters;drpolicies,verbs=get;list;create;update;watch //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;create;update;delete;watch,resourceNames=spoke-clusterrole-bindings diff --git a/controllers/utils/managedclusterview.go b/controllers/utils/managedclusterview.go new file mode 100644 index 00000000..d766ecfa --- /dev/null +++ b/controllers/utils/managedclusterview.go @@ -0,0 +1,69 @@ +package utils + +import ( + "context" + "fmt" + + viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +const MCVLabelKey = "multicluster.odf.openshift.io/cluster" +const MCVNameTemplate = "odf-multicluster-mcv-%s" + +func GetManagedClusterViewName(clusterName string) string { + return fmt.Sprintf(MCVNameTemplate, clusterName) +} + +func CreateOrUpdateManagedClusterView(ctx context.Context, client ctrlClient.Client, resourceToFindName string, resourceToFindNamespace string, resourceToFindType string, clusterName string, ownerRef *metav1.OwnerReference) (*viewv1beta1.ManagedClusterView, controllerutil.OperationResult, error) { + mcv := &viewv1beta1.ManagedClusterView{ + ObjectMeta: metav1.ObjectMeta{ + Name: GetManagedClusterViewName(clusterName), + Namespace: clusterName, + }, + } + + operationResult, err := controllerutil.CreateOrUpdate(ctx, client, mcv, func() error { + mcv.Spec = viewv1beta1.ViewSpec{ + Scope: viewv1beta1.ViewScope{ + Name: resourceToFindName, + Namespace: resourceToFindNamespace, + Resource: resourceToFindType, + }, + } + + if mcv.Labels == nil { + mcv.Labels = make(map[string]string) + } + + mcv.Labels[CreatedByLabelKey] = "odf-multicluster-managedcluster-controller" + + if ownerRef != nil { + mcv.OwnerReferences = []metav1.OwnerReference{*ownerRef} + } + + return nil + }) + + if err != nil { + return nil, controllerutil.OperationResultNone, err + } + + return mcv, operationResult, nil +} + +func GetManagedClusterView(client ctrlClient.Client, name, namespace string) (*viewv1beta1.ManagedClusterView, error) { + mcv := &viewv1beta1.ManagedClusterView{} + err := client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, mcv) + if err != nil { + if errors.IsNotFound(err) { + return nil, fmt.Errorf("ManagedClusterView %s not found in namespace %s", name, namespace) + } + return nil, fmt.Errorf("failed to get ManagedClusterView %s in namespace %s. %w", name, namespace, err) + } + return mcv, nil +} diff --git a/controllers/utils/managedclusterview_test.go b/controllers/utils/managedclusterview_test.go new file mode 100644 index 00000000..2cc357c5 --- /dev/null +++ b/controllers/utils/managedclusterview_test.go @@ -0,0 +1,85 @@ +package utils + +import ( + "context" + "testing" + + viewv1beta1 "github.com/stolostron/multicloud-operators-foundation/pkg/apis/view/v1beta1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestCreateOrUpdateManagedClusterView(t *testing.T) { + s := runtime.NewScheme() + err := viewv1beta1.AddToScheme(s) + assert.NoError(t, err) + client := fake.NewClientBuilder().WithScheme(s).Build() + + t.Run("Success", func(t *testing.T) { + mcv, _, err := CreateOrUpdateManagedClusterView(context.TODO(), client, "example-configmap", "default", "ConfigMap", "managed-cluster-1", nil) + assert.NoError(t, err) + assert.NotNil(t, mcv) + assert.Equal(t, GetManagedClusterViewName("managed-cluster-1"), mcv.Name) + assert.Equal(t, "example-configmap", mcv.Spec.Scope.Name) + assert.Equal(t, "default", mcv.Spec.Scope.Namespace) + assert.Equal(t, "ConfigMap", mcv.Spec.Scope.Resource) + assert.Equal(t, "odf-multicluster-managedcluster-controller", mcv.Labels[CreatedByLabelKey]) + }) +} + +func TestGetManagedClusterView(t *testing.T) { + s := runtime.NewScheme() + err := viewv1beta1.AddToScheme(s) + assert.NoError(t, err) + + clusterView := &viewv1beta1.ManagedClusterView{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-mcv", + Namespace: "test-namespace", + }, + } + + client := fake.NewClientBuilder().WithScheme(s).WithObjects(clusterView).Build() + + tests := []struct { + name string + mcvName string + namespace string + wantErr bool + }{ + { + name: "existing ManagedClusterView", + mcvName: "test-mcv", + namespace: "test-namespace", + wantErr: false, + }, + { + name: "non-existing ManagedClusterView", + mcvName: "non-existing-mcv", + namespace: "test-namespace", + wantErr: true, + }, + { + name: "existing ManagedClusterView in different namespace", + mcvName: "test-mcv", + namespace: "different-namespace", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetManagedClusterView(client, tt.mcvName, tt.namespace) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, got) + assert.Equal(t, tt.mcvName, got.Name) + assert.Equal(t, tt.namespace, got.Namespace) + } + }) + } +} diff --git a/go.mod b/go.mod index eec567e7..b2fe3795 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20240717095253-b12449490cc8 github.com/rook/rook/pkg/apis v0.0.0-20240701212738-8e25300ad55a github.com/spf13/cobra v1.8.0 + github.com/stolostron/multicloud-operators-foundation v0.0.0-20220824091202-e9cd9710d009 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.2.0 diff --git a/go.sum b/go.sum index d8e9bd6f..f03357d4 100644 --- a/go.sum +++ b/go.sum @@ -785,6 +785,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stolostron/multicloud-operators-foundation v0.0.0-20220824091202-e9cd9710d009 h1:/KHe+raMpLL01sYy5adCs95esrfGcHs+IxsI4sd06K0= +github.com/stolostron/multicloud-operators-foundation v0.0.0-20220824091202-e9cd9710d009/go.mod h1:DhSK9KG5nkRnCHLb0QKqRIfnd1Tw+rLgwpCi8n7lIGY= github.com/stolostron/multicloud-operators-placementrule v1.2.4-1-20220311-8eedb3f.0.20230828200208-cd3c119a7fa0 h1:qL6eeBtdjLq7ktBBg8tB44b6jTKQjFy6bdl8EM+Kq6o= github.com/stolostron/multicloud-operators-placementrule v1.2.4-1-20220311-8eedb3f.0.20230828200208-cd3c119a7fa0/go.mod h1:uMTaz9cMLe5N+yJ/PpHPtSOdlBFB00WdxAW+K5TfkVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=