From 1edf4ea080d1da2973f3a8d33de162a2aeb73f53 Mon Sep 17 00:00:00 2001 From: Leela Venkaiah G Date: Wed, 10 Jan 2024 12:59:45 +0530 Subject: [PATCH] controllers: set operatorconditions for upgradeability Signed-off-by: Leela Venkaiah G --- api/v1alpha1/storageclient_types.go | 5 +- ...client-operator.clusterserviceversion.yaml | 13 + .../ocs.openshift.io_storageclients.yaml | 2 + .../ocs.openshift.io_storageclients.yaml | 2 + config/rbac/role.yaml | 7 + config/rbac/status-reporter-clusterrole.yaml | 6 + controllers/storageclient_controller.go | 22 +- controllers/upgrade_controller.go | 231 ++++++++++++++++++ go.mod | 3 +- go.sum | 2 + main.go | 20 ++ pkg/utils/k8sutils.go | 34 +++ service/status-report/main.go | 67 +++-- .../api/pkg/operators/v2/doc.go | 4 + .../api/pkg/operators/v2/groupversion_info.go | 28 +++ .../operators/v2/operatorcondition_types.go | 54 ++++ .../pkg/operators/v2/zz_generated.deepcopy.go | 146 +++++++++++ .../operator-framework/operator-lib/LICENSE | 201 +++++++++++++++ .../operator-lib/conditions/conditions.go | 76 ++++++ .../operator-lib/conditions/factory.go | 119 +++++++++ .../operator-lib/conditions/interface.go | 60 +++++ .../operator-lib/internal/utils/utils.go | 43 ++++ vendor/modules.txt | 5 + 23 files changed, 1093 insertions(+), 57 deletions(-) create mode 100644 controllers/upgrade_controller.go create mode 100644 vendor/github.com/operator-framework/api/pkg/operators/v2/doc.go create mode 100644 vendor/github.com/operator-framework/api/pkg/operators/v2/groupversion_info.go create mode 100644 vendor/github.com/operator-framework/api/pkg/operators/v2/operatorcondition_types.go create mode 100644 vendor/github.com/operator-framework/api/pkg/operators/v2/zz_generated.deepcopy.go create mode 100644 vendor/github.com/operator-framework/operator-lib/LICENSE create mode 100644 vendor/github.com/operator-framework/operator-lib/conditions/conditions.go create mode 100644 vendor/github.com/operator-framework/operator-lib/conditions/factory.go create mode 100644 vendor/github.com/operator-framework/operator-lib/conditions/interface.go create mode 100644 vendor/github.com/operator-framework/operator-lib/internal/utils/utils.go diff --git a/api/v1alpha1/storageclient_types.go b/api/v1alpha1/storageclient_types.go index 28147c765..5f773ccfb 100644 --- a/api/v1alpha1/storageclient_types.go +++ b/api/v1alpha1/storageclient_types.go @@ -48,10 +48,13 @@ type StorageClientSpec struct { // StorageClientStatus defines the observed state of StorageClient type StorageClientStatus struct { + // NOTE: Do not remove "omitempty" from the tags as that'll make mergepatch incompatible + Phase storageClientPhase `json:"phase,omitempty"` // ConsumerID will hold the identity of this cluster inside the attached provider cluster - ConsumerID string `json:"id,omitempty"` + ConsumerID string `json:"id,omitempty"` + DesiredOperatorVersion string `json:"desiredOperatorVersion,omitempty"` } //+kubebuilder:object:root=true diff --git a/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml b/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml index 69762dc35..be6766d15 100644 --- a/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml +++ b/bundle/manifests/ocs-client-operator.clusterserviceversion.yaml @@ -225,6 +225,13 @@ spec: - get - list - watch + - apiGroups: + - operators.coreos.com + resources: + - subscriptions + verbs: + - get + - list - apiGroups: - security.openshift.io resources: @@ -599,6 +606,12 @@ spec: verbs: - get - list + - apiGroups: + - ocs.openshift.io + resources: + - storageclients/status + verbs: + - patch - apiGroups: - "" resources: diff --git a/bundle/manifests/ocs.openshift.io_storageclients.yaml b/bundle/manifests/ocs.openshift.io_storageclients.yaml index 9b6527b7b..61c3393dd 100644 --- a/bundle/manifests/ocs.openshift.io_storageclients.yaml +++ b/bundle/manifests/ocs.openshift.io_storageclients.yaml @@ -56,6 +56,8 @@ spec: status: description: StorageClientStatus defines the observed state of StorageClient properties: + desiredOperatorVersion: + type: string id: description: ConsumerID will hold the identity of this cluster inside the attached provider cluster diff --git a/config/crd/bases/ocs.openshift.io_storageclients.yaml b/config/crd/bases/ocs.openshift.io_storageclients.yaml index 1eaf08141..aa179f5b2 100644 --- a/config/crd/bases/ocs.openshift.io_storageclients.yaml +++ b/config/crd/bases/ocs.openshift.io_storageclients.yaml @@ -58,6 +58,8 @@ spec: status: description: StorageClientStatus defines the observed state of StorageClient properties: + desiredOperatorVersion: + type: string id: description: ConsumerID will hold the identity of this cluster inside the attached provider cluster diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ddf9f6908..80bfe0944 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -197,6 +197,13 @@ rules: - get - list - watch +- apiGroups: + - operators.coreos.com + resources: + - subscriptions + verbs: + - get + - list - apiGroups: - security.openshift.io resources: diff --git a/config/rbac/status-reporter-clusterrole.yaml b/config/rbac/status-reporter-clusterrole.yaml index 7de4c30a0..c9e2ece82 100644 --- a/config/rbac/status-reporter-clusterrole.yaml +++ b/config/rbac/status-reporter-clusterrole.yaml @@ -10,6 +10,12 @@ rules: verbs: - get - list + - apiGroups: + - ocs.openshift.io + resources: + - storageclients/status + verbs: + - patch - apiGroups: - "" resources: diff --git a/controllers/storageclient_controller.go b/controllers/storageclient_controller.go index b7e8f8389..d8e3ea71b 100644 --- a/controllers/storageclient_controller.go +++ b/controllers/storageclient_controller.go @@ -23,14 +23,12 @@ import ( "encoding/json" "fmt" "os" - "strings" "time" "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1" "github.com/red-hat-storage/ocs-client-operator/pkg/utils" configv1 "github.com/openshift/api/config/v1" - opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1" providerClient "github.com/red-hat-storage/ocs-operator/v4/services/provider/client" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -61,8 +59,6 @@ const ( storageClientNameLabel = "ocs.openshift.io/storageclient.name" storageClientNamespaceLabel = "ocs.openshift.io/storageclient.namespace" storageClientFinalizer = "storageclient.ocs.openshift.io" - - csvPrefix = "ocs-client-operator" ) // StorageClientReconciler reconciles a StorageClient object @@ -271,16 +267,10 @@ func (s *StorageClientReconciler) onboardConsumer(instance *v1alpha1.StorageClie return reconcile.Result{}, fmt.Errorf("failed to get the clusterVersion version of the OCP cluster: %v", err) } - // TODO Have a version file corresponding to the release - csvList := opv1a1.ClusterServiceVersionList{} - if err = s.list(&csvList, client.InNamespace(s.OperatorNamespace)); err != nil { - return reconcile.Result{}, fmt.Errorf("failed to list csv resources in ns: %v, err: %v", s.OperatorNamespace, err) - } - csv := utils.Find(csvList.Items, func(csv *opv1a1.ClusterServiceVersion) bool { - return strings.HasPrefix(csv.Name, csvPrefix) - }) - if csv == nil { - return reconcile.Result{}, fmt.Errorf("unable to find csv with prefix %q", csvPrefix) + csv, err := utils.GetCSVByPrefix(s.ctx, s.Client, "ocs-client-operator", s.OperatorNamespace) + if err != nil { + s.Log.Error(err, "failed to get clusterserviceverison of ocs client operator") + return reconcile.Result{}, fmt.Errorf("failed to get clusterserviceversion of ocs client operator: %v", err) } name := fmt.Sprintf("storageconsumer-%s", clusterVersion.Spec.ClusterID) onboardRequest := providerClient.NewOnboardConsumerRequest(). @@ -506,7 +496,3 @@ func (s *StorageClientReconciler) reconcileClientStatusReporterJob(instance *v1a } return reconcile.Result{}, nil } - -func (s *StorageClientReconciler) list(obj client.ObjectList, listOptions ...client.ListOption) error { - return s.Client.List(s.ctx, obj, listOptions...) -} diff --git a/controllers/upgrade_controller.go b/controllers/upgrade_controller.go new file mode 100644 index 000000000..2cb959d0f --- /dev/null +++ b/controllers/upgrade_controller.go @@ -0,0 +1,231 @@ +/* +Copyright 2024 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + "strings" + + "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1" + "github.com/red-hat-storage/ocs-client-operator/pkg/utils" + + "github.com/blang/semver/v4" + "github.com/go-logr/logr" + opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lib/conditions" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + 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/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +const ( + ocsClientOperatorSubscriptionPackageName = "ocs-client-operator" + // TODO(lgangava): there can be "eus" or "fast" or "candidate" supported channels + // and custom channels also might exist, either get this data as part of a configmap + // or cut the name by '-' + subscriptionChannelNamePrefix = "stable-" + controllerName = "upgrade" + upgradeConditionReason = "ConstraintsSatisfied" + dummyPatchVersion = ".99" +) + +type UpgradeReconciler struct { + client.Client + ctx context.Context + log logr.Logger + + Scheme *runtime.Scheme + OperatorCondition conditions.Condition + OperatorNamespace string +} + +func (r *UpgradeReconciler) SetupWithManager(mgr ctrl.Manager) error { + storageClientDesiredVersionPredicate := predicate.Funcs{ + CreateFunc: func(_ event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + if e.ObjectOld == nil || e.ObjectNew == nil { + return false + } + + oldObj, _ := e.ObjectOld.(*v1alpha1.StorageClient) + newObj, _ := e.ObjectNew.(*v1alpha1.StorageClient) + if oldObj == nil || newObj == nil { + return false + } + + return oldObj.Status.DesiredOperatorVersion != newObj.Status.DesiredOperatorVersion + }, + } + ocsClientOperatorSubscriptionPredicate := predicate.Funcs{ + CreateFunc: func(_ event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + if e.ObjectNew == nil { + return false + } + + newObj, _ := e.ObjectNew.(*opv1a1.Subscription) + if newObj == nil { + return false + } + + return newObj.Spec.Package == ocsClientOperatorSubscriptionPackageName + }, + DeleteFunc: func(_ event.DeleteEvent) bool { + return false + }, + } + return ctrl.NewControllerManagedBy(mgr). + Named(controllerName). + Watches(&v1alpha1.StorageClient{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(storageClientDesiredVersionPredicate)). + Watches(&opv1a1.Subscription{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(ocsClientOperatorSubscriptionPredicate)). + Complete(r) +} + +//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,verbs=get;list + +func (r *UpgradeReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + r.ctx = ctx + r.log = log.FromContext(ctx) + + r.log.Info("starting reconcile") + result, err := r.reconcilePhases() + if err != nil { + r.log.Error(err, "an error occurred during reconcilePhases") + } + r.log.Info("reconciling completed") + + return result, err +} + +func (r *UpgradeReconciler) reconcilePhases() (ctrl.Result, error) { + if err := r.reconcileOperatorCondition(); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil +} + +func (r *UpgradeReconciler) reconcileOperatorCondition() error { + + var messages []string + currentVersion, err := r.getCurrentOperatorVersion() + if err != nil { + r.log.Error(err, "failed to get current operator version") + messages = append(messages, "failed to get current operator version") + } + desiredVersion, err := r.getDesiredOperatorVersion() + if err != nil { + r.log.Error(err, "failed to get desired operator version") + messages = append(messages, "failed to get desired operator version") + } + + if len(messages) > 0 { + return r.setOperatorCondition(metav1.ConditionFalse, strings.Join(messages, "; ")) + } + + if currentVersion == nil || desiredVersion == nil { + return r.setOperatorCondition(metav1.ConditionTrue, "No errors are reported.") + } + + if currentVersion.Major > desiredVersion.Major || currentVersion.Minor > desiredVersion.Minor { + return r.setOperatorCondition(metav1.ConditionFalse, "Current operator version is ahead of desired operator version") + } + + return r.setOperatorCondition(metav1.ConditionTrue, "No errors are reported.") +} + +func (r *UpgradeReconciler) setOperatorCondition(isUpgradeable metav1.ConditionStatus, message string) error { + return r.OperatorCondition. + Set(r.ctx, isUpgradeable, conditions.WithReason(upgradeConditionReason), conditions.WithMessage(message)) +} + +// returns current version of the operator based on the subscription +func (r *UpgradeReconciler) getCurrentOperatorVersion() (*semver.Version, error) { + subscriptionList := &opv1a1.SubscriptionList{} + if err := r.list(subscriptionList, client.InNamespace(r.OperatorNamespace)); err != nil { + return nil, err + } + + subscription := utils.Find(subscriptionList.Items, func(sub *opv1a1.Subscription) bool { + return sub.Spec.Package == ocsClientOperatorSubscriptionPackageName + }) + + if subscription == nil { + r.log.Info("subscription of ocs-client-operator doesn't exist") + return nil, nil + } + + channel := subscription.Spec.Channel + version, ok := strings.CutPrefix(channel, subscriptionChannelNamePrefix) + if !ok { + return nil, fmt.Errorf("subscription doesn't refer to a stable channel") + } + // appending patchversion to pass semver checks + version += dummyPatchVersion + + currentVersion, err := semver.Make(version) + if err != nil { + return nil, err + } + + return ¤tVersion, nil +} + +// returns desired version of the operator based on storageclients +func (r *UpgradeReconciler) getDesiredOperatorVersion() (*semver.Version, error) { + storageClientList := &v1alpha1.StorageClientList{} + if err := r.list(storageClientList); err != nil { + return nil, err + } + + if len(storageClientList.Items) == 0 { + r.log.Info("no storageclients exist") + return nil, nil + } + + // appending patchversion to pass semver checks + oldest, err := semver.Make(storageClientList.Items[0].Status.DesiredOperatorVersion + dummyPatchVersion) + if err != nil { + return nil, err + } + + for idx := range storageClientList.Items { + current, err := semver.Make(storageClientList.Items[idx].Status.DesiredOperatorVersion + dummyPatchVersion) + if err != nil { + return nil, err + } + if current.LE(oldest) { + oldest = current + } + } + + return &oldest, nil +} + +func (r *UpgradeReconciler) list(list client.ObjectList, opts ...client.ListOption) error { + return r.Client.List(r.ctx, list, opts...) +} diff --git a/go.mod b/go.mod index 02ef9cc58..a7a34182e 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect + github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -60,6 +60,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect diff --git a/go.sum b/go.sum index 1369a9c18..129299015 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/openshift/api v0.0.0-20231212225112-7cca8a108d7b h1:H7Iu44OPp8/6mp2jS github.com/openshift/api v0.0.0-20231212225112-7cca8a108d7b/go.mod h1:qNtV0315F+f8ld52TLtPvrfivZpdimOzTi3kn9IVbtU= github.com/operator-framework/api v0.20.0 h1:A2YCRhr+6s0k3pRJacnwjh1Ue8BqjIGuQ2jvPg9XCB4= github.com/operator-framework/api v0.20.0/go.mod h1:rXPOhrQ6mMeXqCmpDgt1ALoar9ZlHL+Iy5qut9R99a4= +github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83 h1:TDT89KCBWC5H6oP0w+NL8sQnqR3cXJpr256GnEY/1SA= +github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83/go.mod h1:rLCcQA2GOYpZQg8NbygwQupnuAPLJRetMGG76qvy6so= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/main.go b/main.go index a00c5aa31..7a2fefb3f 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,8 @@ import ( consolev1alpha1 "github.com/openshift/api/console/v1alpha1" secv1 "github.com/openshift/api/security/v1" opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + opv2 "github.com/operator-framework/api/pkg/operators/v2" + "github.com/operator-framework/operator-lib/conditions" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" @@ -61,6 +63,7 @@ func init() { utilruntime.Must(monitoringv1.AddToScheme(scheme)) utilruntime.Must(consolev1alpha1.AddToScheme(scheme)) utilruntime.Must(opv1a1.AddToScheme(scheme)) + utilruntime.Must(opv2.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -163,6 +166,23 @@ func main() { os.Exit(1) } + condition, err := conditions. + InClusterFactory{Client: mgr.GetClient()}. + NewCondition(opv2.ConditionType(opv2.Upgradeable)) + if err != nil { + setupLog.Error(err, "unable to create new upgradeable operator condition") + os.Exit(1) + } + if err = (&controllers.UpgradeReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + OperatorNamespace: utils.GetOperatorNamespace(), + OperatorCondition: condition, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "UpgradeController") + os.Exit(1) + } + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") diff --git a/pkg/utils/k8sutils.go b/pkg/utils/k8sutils.go index 57ac19dd2..e3605623e 100644 --- a/pkg/utils/k8sutils.go +++ b/pkg/utils/k8sutils.go @@ -17,8 +17,15 @@ limitations under the License. package utils import ( + "context" "fmt" "os" + "strings" + + configv1 "github.com/openshift/api/config/v1" + opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) // OperatorNamespaceEnvVar is the constant for env variable OPERATOR_NAMESPACE @@ -60,3 +67,30 @@ func ValidateStausReporterImage() error { return nil } + +// return csv matching the prefix or else error +func GetCSVByPrefix(ctx context.Context, c client.Client, prefix, namespace string) (*opv1a1.ClusterServiceVersion, error) { + csvList := &opv1a1.ClusterServiceVersionList{} + if err := c.List(ctx, csvList, client.InNamespace(namespace)); err != nil { + return nil, fmt.Errorf("failed to list csv resources in namespace: %q, err: %v", namespace, err) + } + csv := Find(csvList.Items, func(csv *opv1a1.ClusterServiceVersion) bool { + return strings.HasPrefix(csv.Name, prefix) && csv.Status.Phase != opv1a1.CSVPhasePending + }) + if csv == nil { + return nil, fmt.Errorf("unable to find csv with prefix %q", prefix) + } + return csv, nil +} + +func GetPlatformVersion(ctx context.Context, c client.Client) (string, error) { + clusterVersion := &configv1.ClusterVersion{} + clusterVersion.Name = "version" + if err := c.Get(ctx, types.NamespacedName{Name: clusterVersion.Name}, clusterVersion); err != nil { + return "", fmt.Errorf("failed to get clusterVersion: %v", err) + } + updated := Find(clusterVersion.Status.History, func(history *configv1.UpdateHistory) bool { + return history.State == configv1.CompletedUpdate + }) + return updated.Version, nil +} diff --git a/service/status-report/main.go b/service/status-report/main.go index adfad169d..04da727d5 100644 --- a/service/status-report/main.go +++ b/service/status-report/main.go @@ -18,8 +18,9 @@ package main import ( "context" + "encoding/json" + "fmt" "os" - "strings" "time" "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1" @@ -37,10 +38,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" ) -const ( - csvPrefix = "ocs-client-operator" -) - func main() { scheme := runtime.NewScheme() if err := v1alpha1.AddToScheme(scheme); err != nil { @@ -92,39 +89,18 @@ func main() { klog.Exitf("Failed to get storageClient %q/%q: %v", storageClient.Namespace, storageClient.Name, err) } - var oprVersion string - csvList := opv1a1.ClusterServiceVersionList{} - if err = cl.List(ctx, &csvList, client.InNamespace(storageClientNamespace)); err != nil { - klog.Warningf("Failed to list csv resources: %v", err) + var operatorVersion string + csv, err := utils.GetCSVByPrefix(ctx, cl, "ocs-client-operator", operatorNamespace) + if err != nil { + klog.Errorf("Failed to get clusterserviceversion: %v", err) } else { - item := utils.Find(csvList.Items, func(csv *opv1a1.ClusterServiceVersion) bool { - return strings.HasPrefix(csv.Name, csvPrefix) - }) - if item != nil { - oprVersion = item.Spec.Version.String() - } - } - if oprVersion == "" { - klog.Warningf("Unable to find csv with prefix %q", csvPrefix) + operatorVersion = csv.Spec.Version.String() } - var pltVersion string - clusterVersion := &configv1.ClusterVersion{} - clusterVersion.Name = "version" - if err = cl.Get(ctx, types.NamespacedName{Name: clusterVersion.Name}, clusterVersion); err != nil { - klog.Warningf("Failed to get clusterVersion: %v", err) - } else { - item := utils.Find(clusterVersion.Status.History, func(record *configv1.UpdateHistory) bool { - return record.State == configv1.CompletedUpdate - }) - if item != nil { - pltVersion = item.Version - } - } - if pltVersion == "" { - klog.Warningf("Unable to find ocp version with completed update") + platformVersion, err := utils.GetPlatformVersion(ctx, cl) + if err != nil { + klog.Errorf("Failed to get platform version: %v", err) } - providerClient, err := providerclient.NewProviderClient( ctx, storageClient.Spec.StorageProviderEndpoint, @@ -136,12 +112,29 @@ func main() { defer providerClient.Close() status := providerclient.NewStorageClientStatus(). - SetPlatformVersion(pltVersion). - SetOperatorVersion(oprVersion) - if _, err = providerClient.ReportStatus(ctx, storageClient.Status.ConsumerID, status); err != nil { + SetPlatformVersion(platformVersion). + SetOperatorVersion(operatorVersion) + statusResponse, err := providerClient.ReportStatus(ctx, storageClient.Status.ConsumerID, status) + if err != nil { klog.Exitf("Failed to report status of storageClient %v: %v", storageClient.Status.ConsumerID, err) } + storageClient.Status = v1alpha1.StorageClientStatus{ + DesiredOperatorVersion: statusResponse.ClientDesiredVersion, + } + jsonPatch, err := json.Marshal(storageClient) + if err != nil { + panic(fmt.Sprintf("failed to marshal storageclient cr: %v", err)) + } + + // patch is being used over update as other fields in status maybe set by storageclient controller in parallel. + // mergepatch is being used over jsonpatch as status.desiredOperatorVersion is initially not present and + // jsonpatch would've required a branch for "add" or "replace" op. + // marshal of status above only encodes desiredVersion as other fields are set to "omitempty" and so mergepatch doesn't overwrite other fields + if err = cl.Status().Patch(ctx, storageClient, client.RawPatch(types.MergePatchType, jsonPatch)); err != nil { + klog.Errorf("Failed to patch storageclient %q desired operator version: %v", storageClient.Name, statusResponse.ClientDesiredVersion) + } + var csiClusterConfigEntry = new(csi.ClusterConfigEntry) scResponse, err := providerClient.GetStorageConfig(ctx, storageClient.Status.ConsumerID) if err != nil { diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v2/doc.go b/vendor/github.com/operator-framework/api/pkg/operators/v2/doc.go new file mode 100644 index 000000000..f85f79242 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v2/doc.go @@ -0,0 +1,4 @@ +// +groupName=operators.coreos.com + +// Package v2 contains resources types for version v2 of the operators.coreos.com API group. +package v2 diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v2/groupversion_info.go b/vendor/github.com/operator-framework/api/pkg/operators/v2/groupversion_info.go new file mode 100644 index 000000000..2d2d923d1 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v2/groupversion_info.go @@ -0,0 +1,28 @@ +// +kubebuilder:object:generate=true + +// Package v2 contains API Schema definitions for the operator v2 API group. +package v2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "operators.coreos.com", Version: "v2"} + + // SchemeGroupVersion is required for compatibility with client generation. + SchemeGroupVersion = GroupVersion + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return GroupVersion.WithResource(resource).GroupResource() +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v2/operatorcondition_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v2/operatorcondition_types.go new file mode 100644 index 000000000..ef1c56de6 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v2/operatorcondition_types.go @@ -0,0 +1,54 @@ +package v2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // Upgradeable indicates that the operator is upgradeable + Upgradeable string = "Upgradeable" +) + +// ConditionType codifies a condition's type. +type ConditionType string + +// OperatorConditionSpec allows an operator to report state to OLM and provides +// cluster admin with the ability to manually override state reported by the operator. +type OperatorConditionSpec struct { + ServiceAccounts []string `json:"serviceAccounts,omitempty"` + Deployments []string `json:"deployments,omitempty"` + Overrides []metav1.Condition `json:"overrides,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// OperatorConditionStatus allows OLM to convey which conditions have been observed. +type OperatorConditionStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +// +kubebuilder:storageversion +// +kubebuilder:resource:shortName=condition,categories=olm +// +kubebuilder:subresource:status +// OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. +type OperatorCondition struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec OperatorConditionSpec `json:"spec,omitempty"` + Status OperatorConditionStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// OperatorConditionList represents a list of Conditions. +type OperatorConditionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []OperatorCondition `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OperatorCondition{}, &OperatorConditionList{}) +} diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v2/zz_generated.deepcopy.go b/vendor/github.com/operator-framework/api/pkg/operators/v2/zz_generated.deepcopy.go new file mode 100644 index 000000000..8f7498c35 --- /dev/null +++ b/vendor/github.com/operator-framework/api/pkg/operators/v2/zz_generated.deepcopy.go @@ -0,0 +1,146 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v2 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorCondition) DeepCopyInto(out *OperatorCondition) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorCondition. +func (in *OperatorCondition) DeepCopy() *OperatorCondition { + if in == nil { + return nil + } + out := new(OperatorCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorCondition) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConditionList) DeepCopyInto(out *OperatorConditionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OperatorCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConditionList. +func (in *OperatorConditionList) DeepCopy() *OperatorConditionList { + if in == nil { + return nil + } + out := new(OperatorConditionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorConditionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConditionSpec) DeepCopyInto(out *OperatorConditionSpec) { + *out = *in + if in.ServiceAccounts != nil { + in, out := &in.ServiceAccounts, &out.ServiceAccounts + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Deployments != nil { + in, out := &in.Deployments, &out.Deployments + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Overrides != nil { + in, out := &in.Overrides, &out.Overrides + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConditionSpec. +func (in *OperatorConditionSpec) DeepCopy() *OperatorConditionSpec { + if in == nil { + return nil + } + out := new(OperatorConditionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorConditionStatus) DeepCopyInto(out *OperatorConditionStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConditionStatus. +func (in *OperatorConditionStatus) DeepCopy() *OperatorConditionStatus { + if in == nil { + return nil + } + out := new(OperatorConditionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/github.com/operator-framework/operator-lib/LICENSE b/vendor/github.com/operator-framework/operator-lib/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-lib/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/operator-framework/operator-lib/conditions/conditions.go b/vendor/github.com/operator-framework/operator-lib/conditions/conditions.go new file mode 100644 index 000000000..7dc365d68 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-lib/conditions/conditions.go @@ -0,0 +1,76 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package conditions + +import ( + "context" + "fmt" + + apiv2 "github.com/operator-framework/api/pkg/operators/v2" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + // ErrNoOperatorCondition indicates that the operator condition CRD is nil + ErrNoOperatorCondition = fmt.Errorf("operator Condition CRD is nil") +) + +// condition is a Condition that gets and sets a specific +// conditionType in the OperatorCondition CR. +type condition struct { + namespacedName types.NamespacedName + condType apiv2.ConditionType + client client.Client +} + +var _ Condition = &condition{} + +// Get implements conditions.Get +func (c *condition) Get(ctx context.Context) (*metav1.Condition, error) { + operatorCond := &apiv2.OperatorCondition{} + err := c.client.Get(ctx, c.namespacedName, operatorCond) + if err != nil { + return nil, err + } + con := meta.FindStatusCondition(operatorCond.Spec.Conditions, string(c.condType)) + + if con == nil { + return nil, fmt.Errorf("conditionType %v not found", c.condType) + } + return con, nil +} + +// Set implements conditions.Set +func (c *condition) Set(ctx context.Context, status metav1.ConditionStatus, option ...Option) error { + operatorCond := &apiv2.OperatorCondition{} + err := c.client.Get(ctx, c.namespacedName, operatorCond) + if err != nil { + return err + } + + newCond := &metav1.Condition{ + Type: string(c.condType), + Status: status, + } + + for _, opt := range option { + opt(newCond) + } + meta.SetStatusCondition(&operatorCond.Spec.Conditions, *newCond) + return c.client.Update(ctx, operatorCond) +} diff --git a/vendor/github.com/operator-framework/operator-lib/conditions/factory.go b/vendor/github.com/operator-framework/operator-lib/conditions/factory.go new file mode 100644 index 000000000..64ff9bb9a --- /dev/null +++ b/vendor/github.com/operator-framework/operator-lib/conditions/factory.go @@ -0,0 +1,119 @@ +// Copyright 2021 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package conditions + +import ( + "fmt" + "os" + + apiv2 "github.com/operator-framework/api/pkg/operators/v2" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/operator-framework/operator-lib/internal/utils" +) + +// Factory define the interface for building Conditions. +type Factory interface { + NewCondition(apiv2.ConditionType) (Condition, error) + GetNamespacedName() (*types.NamespacedName, error) +} + +// InClusterFactory is a conditions factory that can build conditions and get +// the namespaced name of the operator's condition based on an in-cluster +// configuration. +type InClusterFactory struct { + Client client.Client +} + +// NewCondition creates a new Condition using the provided client and condition +// type. The condition's name and namespace are determined by the Factory's GetName +// and GetNamespace functions. +func (f InClusterFactory) NewCondition(condType apiv2.ConditionType) (Condition, error) { + objKey, err := f.GetNamespacedName() + if err != nil { + return nil, err + } + return &condition{ + namespacedName: *objKey, + condType: condType, + client: f.Client, + }, nil +} + +// GetNamespacedName returns the NamespacedName of the CR. It returns an error +// when the name of the CR cannot be found from the environment variable set by +// OLM. Hence, GetNamespacedName() can provide the NamespacedName when the operator +// is running on cluster and is being managed by OLM. +func (f InClusterFactory) GetNamespacedName() (*types.NamespacedName, error) { + conditionName, err := f.getConditionName() + if err != nil { + return nil, fmt.Errorf("get operator condition name: %v", err) + } + conditionNamespace, err := f.getConditionNamespace() + if err != nil { + return nil, fmt.Errorf("get operator condition namespace: %v", err) + } + + return &types.NamespacedName{Name: conditionName, Namespace: conditionNamespace}, nil +} + +const ( + // operatorCondEnvVar is the env variable which + // contains the name of the Condition CR associated to the operator, + // set by OLM. + operatorCondEnvVar = "OPERATOR_CONDITION_NAME" +) + +// getConditionName reads and returns the OPERATOR_CONDITION_NAME environment +// variable. If the variable is unset or empty, it returns an error. +func (f InClusterFactory) getConditionName() (string, error) { + name := os.Getenv(operatorCondEnvVar) + if name == "" { + return "", fmt.Errorf("could not determine operator condition name: environment variable %s not set", operatorCondEnvVar) + } + return name, nil +} + +// readNamespace gets the namespacedName of the operator. +var readNamespace = utils.GetOperatorNamespace + +// getConditionNamespace reads the namespace file mounted into a pod in a +// cluster via its service account volume. If the file is not found or cannot be +// read, this function returns an error. +func (f InClusterFactory) getConditionNamespace() (string, error) { + return readNamespace() +} + +// NewCondition returns a new Condition interface using the provided client +// for the specified conditionType. The condition will internally fetch the namespacedName +// of the operatorConditionCRD. +// +// Deprecated: Use InClusterFactory{cl}.NewCondition() instead. +func NewCondition(cl client.Client, condType apiv2.ConditionType) (Condition, error) { + return InClusterFactory{cl}.NewCondition(condType) +} + +// GetNamespacedName returns the NamespacedName of the CR. It returns an error +// when the name of the CR cannot be found from the environment variable set by +// OLM. Hence, GetNamespacedName() can provide the NamespacedName when the operator +// is running on cluster and is being managed by OLM. If running locally, operator +// writers are encouraged to skip this method or gracefully handle the errors by logging +// a message. +// +// Deprecated: InClusterFactory{}.GetNamespacedName(). +func GetNamespacedName() (*types.NamespacedName, error) { + return InClusterFactory{}.GetNamespacedName() +} diff --git a/vendor/github.com/operator-framework/operator-lib/conditions/interface.go b/vendor/github.com/operator-framework/operator-lib/conditions/interface.go new file mode 100644 index 000000000..37e9d8711 --- /dev/null +++ b/vendor/github.com/operator-framework/operator-lib/conditions/interface.go @@ -0,0 +1,60 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package conditions + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Condition can Get and Set a conditionType in an Operator Condition custom resource +// associated with the operator. +type Condition interface { + // Get fetches the condition on the operator's + // OperatorCondition. It returns an error if there are problems getting + // the OperatorCondition object or if the specific condition type does not + // exist. + Get(ctx context.Context) (*metav1.Condition, error) + + // Set sets the specific condition on the operator's + // OperatorCondition to the provided status. If the condition is not + // present, it is added to the CR. + // To set a new condition, the user can call this method and provide optional + // parameters if required. It returns an error if there are problems getting or + // updating the OperatorCondition object. + Set(ctx context.Context, status metav1.ConditionStatus, option ...Option) error +} + +// Option is a function that applies a change to a condition. +// This can be used to set optional condition fields, like reasons +// and messages. +type Option func(*metav1.Condition) + +// WithReason is an Option, which adds the reason +// to the condition. +func WithReason(reason string) Option { + return func(c *metav1.Condition) { + c.Reason = reason + } +} + +// WithMessage is an Option, which adds the reason +// to the condition. +func WithMessage(message string) Option { + return func(c *metav1.Condition) { + c.Message = message + } +} diff --git a/vendor/github.com/operator-framework/operator-lib/internal/utils/utils.go b/vendor/github.com/operator-framework/operator-lib/internal/utils/utils.go new file mode 100644 index 000000000..fb1f5985a --- /dev/null +++ b/vendor/github.com/operator-framework/operator-lib/internal/utils/utils.go @@ -0,0 +1,43 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "fmt" + "os" + "strings" +) + +// ErrNoNamespace indicates that a namespace could not be found for the current +// environment +var ErrNoNamespace = fmt.Errorf("namespace not found for current environment") + +var readSAFile = func() ([]byte, error) { + return os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") +} + +// GetOperatorNamespace returns the namespace the operator should be running in from +// the associated service account secret. +var GetOperatorNamespace = func() (string, error) { + nsBytes, err := readSAFile() + if err != nil { + if os.IsNotExist(err) { + return "", ErrNoNamespace + } + return "", err + } + ns := strings.TrimSpace(string(nsBytes)) + return ns, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1be4bed85..4f0f0c223 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -153,6 +153,11 @@ github.com/openshift/api/security/v1 github.com/operator-framework/api/pkg/lib/version github.com/operator-framework/api/pkg/operators github.com/operator-framework/api/pkg/operators/v1alpha1 +github.com/operator-framework/api/pkg/operators/v2 +# github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83 +## explicit; go 1.20 +github.com/operator-framework/operator-lib/conditions +github.com/operator-framework/operator-lib/internal/utils # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors