Repository: operator-framework/kubectl-operator
Pull request #225: ✨ Add command to update olmv1 operator
Author: login: azych
Labels: name: lgtm
description: Indicates that a PR is ready to be merged.
Created: 2025-03-05T11:51:44Z | Merged: 2025-03-12T18:12:20Z

Adds command to update an existing olmv1 operator (`ClusterExtension`).

Command has the following signature and flags:
```bash
Update an operator

Usage:
  operator olmv1 update operator <operator> [flags]

Flags:
      --channels stringArray               desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration
  -h, --help                               help for operator
      --ignore-unset                       when enabled, any unset flag value will not be changed. Disabling means that for each unset value a default will be used instead (default true)
      --labels stringToString              labels that will be set on the operator (default [])
      --selector string                    filters the set of catalogs used in the bundle selection process. Empty means that all catalogs will be used in the bundle selection process
      --upgrade-constraint-policy string   controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided
      --version string                     desired operator version (single or range) in semVer format. AND operation with channels

```

Example walkthrough:
```bash
➜ kubectl get clusterextensions
NAME        INSTALLED BUNDLE             VERSION   INSTALLED   PROGRESSING   AGE
zookeeper   zookeeper-operator.v0.17.8   0.17.8    True        True          44s
➜ go run *.go olmv1 update operator zookeeper
operator "zookeeper" updated
➜ kubectl get clusterextensions
NAME        INSTALLED BUNDLE              VERSION   INSTALLED   PROGRESSING   AGE
zookeeper   zookeeper-operator.v0.17.10   0.17.10   True        True          77s

➜ kubectl get clusterextensions
NAME        INSTALLED BUNDLE             VERSION   INSTALLED   PROGRESSING   AGE
zookeeper   zookeeper-operator.v0.17.8   0.17.8    True        True          8s
➜ go run *.go olmv1 update operator zookeeper --version=0.17.8
failed to update operator: no changes detected - operator already in desired state
exit status 1

➜ kubectl get clusterextensions zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 1
  name: zookeeper
  resourceVersion: "4087"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      packageName: zookeeper-operator
      upgradeConstraintPolicy: CatalogProvided
      version: 0.17.8
    sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --version=0.17.8 --upgrade-constraint-policy=SelfCertified
operator "zookeeper" updated
➜ kubectl get clusterextensions zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 2
  name: zookeeper
  resourceVersion: "4328"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      packageName: zookeeper-operator
      upgradeConstraintPolicy: SelfCertified
      version: 0.17.8
    sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --override-unset-with-current
failed to update operator: no changes detected - operator already in desired state
exit status 1
➜ go run *.go olmv1 update operator zookeeper --version=0.17.8 --override-unset-with-current
failed to update operator: no changes detected - operator already in desired state
exit status 1
➜ go run *.go olmv1 update operator zookeeper
operator "zookeeper" updated
➜ kubectl get clusterextensions zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 3
  name: zookeeper
  resourceVersion: "4642"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      packageName: zookeeper-operator
      upgradeConstraintPolicy: CatalogProvided
      version: 0.17.8
    sourceType: Catalog

➜ go run *.go olmv1 update operator zookeeper --version=0.17.10 --channels=alpha
operator "zookeeper" updated
➜ kubectl get clusterextensions zookeeper
NAME        INSTALLED BUNDLE              VERSION   INSTALLED   PROGRESSING   AGE
zookeeper   zookeeper-operator.v0.17.10   0.17.10   True        True          6m
➜ kubectl get clusterextensions zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 5
  name: zookeeper
  resourceVersion: "4973"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      channels:
      - alpha
      packageName: zookeeper-operator
      upgradeConstraintPolicy: CatalogProvided
      version: 0.17.10
    sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --version=0.17.10
operator "zookeeper" updated
➜ kubectl get clusterextensions zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 6
  name: zookeeper
  resourceVersion: "5375"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      packageName: zookeeper-operator
      upgradeConstraintPolicy: CatalogProvided
      version: 0.17.10
    sourceType: Catalog

➜ go run *.go olmv1 update operator zookeeper --version="17-0-0"
failed to update operator: failed parsing version: Could not parse Range "17-0-0": Could not parse version "17-0-0" in "17-0-0": No Major.Minor.Patch elements found
exit status 1
➜ go run *.go olmv1 update operator zookeeper --version=">=0.17.9"
operator "zookeeper" updated
➜ kubectl get clusterextension zookeeper -o yaml
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
  creationTimestamp: "2025-03-05T10:21:08Z"
  finalizers:
  - olm.operatorframework.io/cleanup-unpack-cache
  - olm.operatorframework.io/cleanup-contentmanager-cache
  generation: 11
  name: zookeeper
  resourceVersion: "13986"
  uid: 7d808271-dffa-4395-a93c-12148959a6fe
spec:
  namespace: zookeeper
  serviceAccount:
    name: zookeeper
  source:
    catalog:
      packageName: zookeeper-operator
      upgradeConstraintPolicy: CatalogProvided
      version: '>=0.17.9'
    sourceType: Catalog

➜ go run *.go olmv1 update operator zookeeper --version="0.17.8" --selector="notfound,doesnotexist"
operator "zookeeper" updated
➜ kubectl get clusterextensions -o yaml
apiVersion: v1
items:
- apiVersion: olm.operatorframework.io/v1
  kind: ClusterExtension
  metadata:
    creationTimestamp: "2025-03-12T09:08:11Z"
    finalizers:
    - olm.operatorframework.io/cleanup-unpack-cache
    - olm.operatorframework.io/cleanup-contentmanager-cache
    generation: 2
    name: zookeeper
    resourceVersion: "11202"
    uid: 04f1a520-2d03-4c8e-b6c7-2aae43a5025c
  spec:
    namespace: zookeeper
    serviceAccount:
      name: zookeeper
    source:
      catalog:
        packageName: zookeeper-operator
        selector:
          matchExpressions:
          - key: doesnotexist
            operator: Exists
          - key: notfound
            operator: Exists
        upgradeConstraintPolicy: CatalogProvided
        version: 0.17.8
      sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --version="0.17.8" --selector="notfound,doesnotexist"
failed to update operator: no changes detected - operator already in desired state
exit status 1
➜ go run *.go olmv1 update operator zookeeper --version="0.17.8" --selector="notfound,doesnotexist2"
operator "zookeeper" updated
➜ kubectl get clusterextensions -o yaml
apiVersion: v1
items:
- apiVersion: olm.operatorframework.io/v1
  kind: ClusterExtension
  metadata:
    creationTimestamp: "2025-03-12T09:08:11Z"
    finalizers:
    - olm.operatorframework.io/cleanup-unpack-cache
    - olm.operatorframework.io/cleanup-contentmanager-cache
    generation: 3
    name: zookeeper
    resourceVersion: "11250"
    uid: 04f1a520-2d03-4c8e-b6c7-2aae43a5025c
  spec:
    namespace: zookeeper
    serviceAccount:
      name: zookeeper
    source:
      catalog:
        packageName: zookeeper-operator
        selector:
          matchExpressions:
          - key: doesnotexist2
            operator: Exists
          - key: notfound
            operator: Exists
        upgradeConstraintPolicy: CatalogProvided
        version: 0.17.8
      sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --version="0.17.8"
failed to update operator: no changes detected - operator already in desired state
exit status 1
➜ go run *.go olmv1 update operator zookeeper --version="0.17.8" --ignore-unset=false
operator "zookeeper" updated
➜ kubectl get clusterextensions -o yaml
apiVersion: v1
items:
- apiVersion: olm.operatorframework.io/v1
  kind: ClusterExtension
  metadata:
    creationTimestamp: "2025-03-12T09:08:11Z"
    finalizers:
    - olm.operatorframework.io/cleanup-unpack-cache
    - olm.operatorframework.io/cleanup-contentmanager-cache
    generation: 4
    name: zookeeper
    resourceVersion: "11299"
    uid: 04f1a520-2d03-4c8e-b6c7-2aae43a5025c
  spec:
    namespace: zookeeper
    serviceAccount:
      name: zookeeper
    source:
      catalog:
        packageName: zookeeper-operator
        upgradeConstraintPolicy: CatalogProvided
        version: 0.17.8
      sourceType: Catalog
➜ go run *.go olmv1 update operator zookeeper --version="0.17.8" --selector="<"
failed to update operator: failed parsing selector: couldn't parse the selector string "<": found '<', expected: !, identifier, or 'end of string'
exit status 1
```

Closes https://github.com/operator-framework/operator-controller/issues/1772


Diff:
diff --git a/internal/cmd/internal/olmv1/operator_update.go b/internal/cmd/internal/olmv1/operator_update.go
new file mode 100644
index 00000000..c0a76c28
--- /dev/null
+++ b/internal/cmd/internal/olmv1/operator_update.go
@@ -0,0 +1,42 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semVer format. AND operation with channels")
+	fs.StringVar(&i.Selector, "selector", "", "filters the set of catalogs used in the bundle selection process. Empty means that all catalogs will be used in the bundle selection process")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.IgnoreUnset, "ignore-unset", true, "when enabled, any unset flag value will not be changed. Disabling means that for each unset value a default will be used instead")
+}
diff --git a/internal/cmd/olmv1.go b/internal/cmd/olmv1.go
index 8e7c39d3..a1a0defb 100644
--- a/internal/cmd/olmv1.go
+++ b/internal/cmd/olmv1.go
@@ -38,12 +38,22 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	deleteCmd.AddCommand(olmv1.NewCatalogDeleteCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
+		Short: "Update a resource",
+		Long:  "Update a resource",
+	}
+	updateCmd.AddCommand(
+		olmv1.NewOperatorUpdateCmd(cfg),
+	)
+
 	cmd.AddCommand(
 		olmv1.NewOperatorInstallCmd(cfg),
 		olmv1.NewOperatorUninstallCmd(cfg),
 		getCmd,
 		createCmd,
 		deleteCmd,
+		updateCmd,
 	)
 
 	return cmd
diff --git a/internal/pkg/v1/action/action_suite_test.go b/internal/pkg/v1/action/action_suite_test.go
index ab81ec06..e01046b2 100644
--- a/internal/pkg/v1/action/action_suite_test.go
+++ b/internal/pkg/v1/action/action_suite_test.go
@@ -8,7 +8,9 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
+	apimeta "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"
 
 	olmv1 "github.com/operator-framework/operator-controller/api/v1"
@@ -63,3 +65,69 @@ func newClusterCatalog(name string) *olmv1.ClusterCatalog {
 		ObjectMeta: metav1.ObjectMeta{Name: name},
 	}
 }
+
+type extensionOpt func(*olmv1.ClusterExtension)
+
+func withVersion(version string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.Catalog.Version = version
+	}
+}
+
+func withSourceType(sourceType string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.SourceType = sourceType
+	}
+}
+
+// nolint: unparam
+func withConstraintPolicy(policy string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.Catalog.UpgradeConstraintPolicy = olmv1.UpgradeConstraintPolicy(policy)
+	}
+}
+
+func withChannels(channels ...string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.Catalog.Channels = channels
+	}
+}
+
+func withLabels(labels map[string]string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.SetLabels(labels)
+	}
+}
+
+func buildExtension(packageName string, opts ...extensionOpt) *olmv1.ClusterExtension {
+	ext := &olmv1.ClusterExtension{
+		Spec: olmv1.ClusterExtensionSpec{
+			Source: olmv1.SourceConfig{
+				Catalog: &olmv1.CatalogFilter{PackageName: packageName},
+			},
+		},
+	}
+	ext.SetName(packageName)
+	for _, opt := range opts {
+		opt(ext)
+	}
+
+	return ext
+}
+
+func updateOperatorConditionStatus(name string, cl client.Client, typ string, status metav1.ConditionStatus) error {
+	var ext olmv1.ClusterExtension
+	key := types.NamespacedName{Name: name}
+
+	if err := cl.Get(context.TODO(), key, &ext); err != nil {
+		return err
+	}
+
+	apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{
+		Type:               typ,
+		Status:             status,
+		ObservedGeneration: ext.GetGeneration(),
+	})
+
+	return cl.Update(context.TODO(), &ext)
+}
diff --git a/internal/pkg/v1/action/catalog_delete.go b/internal/pkg/v1/action/catalog_delete.go
index 8d5bb580..d3888271 100644
--- a/internal/pkg/v1/action/catalog_delete.go
+++ b/internal/pkg/v1/action/catalog_delete.go
@@ -28,7 +28,7 @@ func NewCatalogDelete(cfg *action.Configuration) *CatalogDelete {
 func (cd *CatalogDelete) Run(ctx context.Context) ([]string, error) {
 	// validate
 	if cd.DeleteAll && cd.CatalogName != "" {
-		return nil, errNameAndSelector
+		return nil, ErrNameAndSelector
 	}
 
 	// delete single, specified catalog
@@ -42,7 +42,7 @@ func (cd *CatalogDelete) Run(ctx context.Context) ([]string, error) {
 		return nil, err
 	}
 	if len(catatalogList.Items) == 0 {
-		return nil, errNoResourcesFound
+		return nil, ErrNoResourcesFound
 	}
 
 	errs := make([]error, 0, len(catatalogList.Items))
diff --git a/internal/pkg/v1/action/errors.go b/internal/pkg/v1/action/errors.go
index d8cea85c..194562d7 100644
--- a/internal/pkg/v1/action/errors.go
+++ b/internal/pkg/v1/action/errors.go
@@ -3,6 +3,7 @@ package action
 import "errors"
 
 var (
-	errNoResourcesFound = errors.New("no resources found")
-	errNameAndSelector  = errors.New("name cannot be provided when a selector is specified")
+	ErrNoResourcesFound = errors.New("no resources found")
+	ErrNameAndSelector  = errors.New("name cannot be provided when a selector is specified")
+	ErrNoChange         = errors.New("no changes detected - operator already in desired state")
 )
diff --git a/internal/pkg/v1/action/helpers.go b/internal/pkg/v1/action/helpers.go
index 084f82aa..e3002300 100644
--- a/internal/pkg/v1/action/helpers.go
+++ b/internal/pkg/v1/action/helpers.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"slices"
+	"strings"
 	"time"
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -46,20 +47,26 @@ func waitUntilCatalogStatusCondition(
 	})
 }
 
-func waitForDeletion(ctx context.Context, cl client.Client, obj client.Object) error {
-	key := objectKeyForObject(obj)
-	if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
-		if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) {
-			return true, nil
-		} else if err != nil {
+func waitUntilOperatorStatusCondition(
+	ctx context.Context,
+	cl getter,
+	operator *olmv1.ClusterExtension,
+	conditionType string,
+	conditionStatus metav1.ConditionStatus,
+) error {
+	opKey := objectKeyForObject(operator)
+	return wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
+		if err := cl.Get(conditionCtx, opKey, operator); err != nil {
 			return false, err
 		}
-		return false, nil
-	}); err != nil {
-		return fmt.Errorf("waiting for deletion: %w", err)
-	}
 
-	return nil
+		if slices.ContainsFunc(operator.Status.Conditions, func(cond metav1.Condition) bool {
+			return cond.Type == conditionType && cond.Status == conditionStatus
+		}) {
+			return true, nil
+		}
+		return false, nil
+	})
 }
 
 func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) error {
@@ -72,3 +79,22 @@ func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) err
 
 	return nil
 }
+
+func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error {
+	for _, obj := range objs {
+		obj := obj
+		lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
+		key := objectKeyForObject(obj)
+		if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
+			if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) {
+				return true, nil
+			} else if err != nil {
+				return false, err
+			}
+			return false, nil
+		}); err != nil {
+			return fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err)
+		}
+	}
+	return nil
+}
diff --git a/internal/pkg/v1/action/operator_update.go b/internal/pkg/v1/action/operator_update.go
new file mode 100644
index 00000000..830ae5d5
--- /dev/null
+++ b/internal/pkg/v1/action/operator_update.go
@@ -0,0 +1,146 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"maps"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	cfg *action.Configuration
+
+	Package string
+
+	Version  string
+	Channels []string
+	Selector string
+	// parsedSelector is used internally to avoid potentially costly transformations
+	// between string and metav1.LabelSelector formats
+	parsedSelector          *metav1.LabelSelector
+	UpgradeConstraintPolicy string
+	Labels                  map[string]string
+	IgnoreUnset             bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		cfg:  cfg,
+		Logf: func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+	var err error
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err = ou.cfg.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext)
+
+	if ou.Version != "" {
+		if _, err = semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+	if ou.Selector != "" && ou.parsedSelector == nil {
+		ou.parsedSelector, err = metav1.ParseToLabelSelector(ou.Selector)
+		if err != nil {
+			return nil, fmt.Errorf("failed parsing selector: %w", err)
+		}
+	}
+
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext, constraintPolicy) {
+		return nil, ErrNoChange
+	}
+
+	ou.prepareUpdatedExtension(&ext, constraintPolicy)
+	if err := ou.cfg.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.cfg.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(ext olmv1.ClusterExtension) {
+	if !ou.IgnoreUnset {
+		if ou.UpgradeConstraintPolicy == "" {
+			ou.UpgradeConstraintPolicy = string(olmv1.UpgradeConstraintPolicyCatalogProvided)
+		}
+
+		return
+	}
+
+	// IgnoreUnset is enabled
+	// set all unset values to what they are on the current object
+	catalogSrc := ext.Spec.Source.Catalog
+	if ou.Version == "" {
+		ou.Version = catalogSrc.Version
+	}
+	if len(ou.Channels) == 0 {
+		ou.Channels = catalogSrc.Channels
+	}
+	if ou.UpgradeConstraintPolicy == "" {
+		ou.UpgradeConstraintPolicy = string(catalogSrc.UpgradeConstraintPolicy)
+	}
+	if len(ou.Labels) == 0 {
+		ou.Labels = ext.Labels
+	}
+	if ou.Selector == "" && catalogSrc.Selector != nil {
+		ou.parsedSelector = catalogSrc.Selector
+	}
+}
+
+func (ou *OperatorUpdate) needsUpdate(ext olmv1.ClusterExtension, constraintPolicy olmv1.UpgradeConstraintPolicy) bool {
+	catalogSrc := ext.Spec.Source.Catalog
+
+	// object string form is used for comparison to:
+	// - remove the need for potentially costly metav1.FormatLabelSelector calls
+	// - avoid having to handle potential reordering of items from on cluster state
+	sameSelectors := (catalogSrc.Selector == nil && ou.parsedSelector == nil) ||
+		(catalogSrc.Selector != nil && ou.parsedSelector != nil &&
+			catalogSrc.Selector.String() == ou.parsedSelector.String())
+
+	if catalogSrc.Version == ou.Version &&
+		slices.Equal(catalogSrc.Channels, ou.Channels) &&
+		catalogSrc.UpgradeConstraintPolicy == constraintPolicy &&
+		maps.Equal(ext.Labels, ou.Labels) &&
+		sameSelectors {
+		return false
+	}
+
+	return true
+}
+
+func (ou *OperatorUpdate) prepareUpdatedExtension(ext *olmv1.ClusterExtension, constraintPolicy olmv1.UpgradeConstraintPolicy) {
+	ext.SetLabels(ou.Labels)
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Selector = ou.parsedSelector
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+}
diff --git a/internal/pkg/v1/action/operator_update_test.go b/internal/pkg/v1/action/operator_update_test.go
new file mode 100644
index 00000000..f4459f7b
--- /dev/null
+++ b/internal/pkg/v1/action/operator_update_test.go
@@ -0,0 +1,189 @@
+package action_test
+
+import (
+	"context"
+	"maps"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+var _ = Describe("OperatorUpdate", func() {
+	setupEnv := func(extensions ...client.Object) action.Configuration {
+		var cfg action.Configuration
+
+		sch, err := action.NewScheme()
+		Expect(err).To(BeNil())
+
+		cl := fake.NewClientBuilder().
+			WithObjects(extensions...).
+			WithScheme(sch).
+			Build()
+		cfg.Scheme = sch
+		cfg.Client = cl
+
+		return cfg
+	}
+
+	It("fails finding existing operator", func() {
+		cfg := setupEnv()
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "does-not-exist"
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).NotTo(BeNil())
+		Expect(err.Error()).To(ContainSubstring("not found"))
+		Expect(ext).To(BeNil())
+	})
+
+	It("fails to handle operator with non-catalog source type", func() {
+		cfg := setupEnv(buildExtension("test", withSourceType("unknown")))
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).NotTo(BeNil())
+		Expect(err.Error()).To(ContainSubstring("unrecognized source type"))
+		Expect(ext).To(BeNil())
+	})
+
+	It("fails because desired operator state matches current", func() {
+		cfg := setupEnv(buildExtension(
+			"test",
+			withSourceType(olmv1.SourceTypeCatalog),
+			withConstraintPolicy(string(olmv1.UpgradeConstraintPolicyCatalogProvided))),
+		)
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).NotTo(BeNil())
+		Expect(err).To(MatchError(internalaction.ErrNoChange))
+		Expect(ext).To(BeNil())
+	})
+
+	It("fails because desired operator state matches current with IgnoreUnset enabled", func() {
+		cfg := setupEnv(buildExtension(
+			"test",
+			withSourceType(olmv1.SourceTypeCatalog),
+			withConstraintPolicy(string(olmv1.UpgradeConstraintPolicyCatalogProvided)),
+			withChannels("a", "b"),
+			withLabels(map[string]string{"c": "d"}),
+			withVersion("10.0.4"),
+		))
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		updater.IgnoreUnset = true
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).NotTo(BeNil())
+		Expect(err).To(MatchError(internalaction.ErrNoChange))
+		Expect(ext).To(BeNil())
+	})
+
+	It("fails validating operator version", func() {
+		cfg := setupEnv(buildExtension(
+			"test",
+			withSourceType(olmv1.SourceTypeCatalog),
+			withConstraintPolicy(string(olmv1.UpgradeConstraintPolicyCatalogProvided))),
+		)
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		updater.Version = "10-4"
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).NotTo(BeNil())
+		Expect(err.Error()).To(ContainSubstring("parsing version"))
+		Expect(ext).To(BeNil())
+	})
+
+	It("fails updating operator", func() {
+		testExt := buildExtension(
+			"test",
+			withSourceType(olmv1.SourceTypeCatalog),
+			withConstraintPolicy(string(olmv1.UpgradeConstraintPolicyCatalogProvided)),
+		)
+		cfg := setupEnv(testExt)
+
+		ctx, cancel := context.WithCancel(context.TODO())
+		cancel()
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		updater.Version = "10.0.4"
+		updater.Channels = []string{"a", "b"}
+		updater.Labels = map[string]string{"c": "d"}
+		updater.UpgradeConstraintPolicy = string(olmv1.UpgradeConstraintPolicySelfCertified)
+		ext, err := updater.Run(ctx)
+
+		Expect(err).NotTo(BeNil())
+		Expect(err.Error()).To(ContainSubstring("timed out"))
+		Expect(ext).To(BeNil())
+	})
+
+	It("successfully updates operator", func() {
+		testExt := buildExtension(
+			"test",
+			withSourceType(olmv1.SourceTypeCatalog),
+			withConstraintPolicy(string(olmv1.UpgradeConstraintPolicyCatalogProvided)),
+		)
+		cfg := setupEnv(testExt, buildExtension("test2"), buildExtension("test3"))
+
+		go func() {
+			Eventually(updateOperatorConditionStatus("test", cfg.Client, olmv1.TypeInstalled, metav1.ConditionTrue)).
+				WithTimeout(5 * time.Second).WithPolling(200 * time.Second).
+				Should(Succeed())
+		}()
+
+		updater := internalaction.NewOperatorUpdate(&cfg)
+		updater.Package = "test"
+		updater.Version = "10.0.4"
+		updater.Channels = []string{"a", "b"}
+		updater.Labels = map[string]string{"c": "d"}
+		updater.UpgradeConstraintPolicy = string(olmv1.UpgradeConstraintPolicySelfCertified)
+		ext, err := updater.Run(context.TODO())
+
+		Expect(err).To(BeNil())
+		Expect(ext).NotTo(BeNil())
+		Expect(ext.Spec.Source.Catalog.Version).To(Equal(updater.Version))
+		Expect(maps.Equal(ext.Labels, updater.Labels)).To(BeTrue())
+		Expect(ext.Spec.Source.Catalog.Channels).To(ContainElements(updater.Channels))
+		Expect(ext.Spec.Source.Catalog.UpgradeConstraintPolicy).
+			To(Equal(olmv1.UpgradeConstraintPolicy(updater.UpgradeConstraintPolicy)))
+
+		// also verify that other objects were not updated
+		validateNonUpdatedExtensions(cfg.Client, "test")
+	})
+})
+
+func validateNonUpdatedExtensions(c client.Client, exceptName string) {
+	var extList olmv1.ClusterExtensionList
+	err := c.List(context.TODO(), &extList)
+	Expect(err).To(BeNil())
+
+	for _, ext := range extList.Items {
+		if ext.Name == exceptName {
+			continue
+		}
+
+		Expect(ext.Spec.Source.Catalog.Version).To(BeEmpty())
+		Expect(ext.Labels).To(BeEmpty())
+		Expect(ext.Spec.Source.Catalog.Channels).To(BeEmpty())
+		Expect(ext.Spec.Source.Catalog.UpgradeConstraintPolicy).To(BeEmpty())
+	}
+}


Reviews:
user: login: azych
body: Like in https://github.com/operator-framework/kubectl-operator/pull/220, I'll hold off on unit tests for now
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:00:00Z
user: login: LalatenduMohanty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T21:05:35Z
user: login: LalatenduMohanty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T21:06:18Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T08:41:36Z
user: login: LalatenduMohanty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T13:46:40Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T14:06:36Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T14:53:05Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T14:57:35Z
user: login: ankitathomas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T22:05:59Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T08:08:10Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:16:44Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:17:35Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:18:00Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:20:51Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:24:27Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:30:21Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T11:33:14Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:21:47Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:27:27Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:30:19Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:34:43Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:47:20Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T12:48:09Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:45:19Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:45:59Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:53:25Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:54:39Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:56:11Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:56:28Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T15:01:17Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T15:05:06Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T15:40:43Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T15:42:53Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T15:43:48Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T16:26:50Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T16:27:52Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T16:32:05Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T16:56:17Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T17:04:07Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T17:06:43Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T18:10:05Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T18:19:35Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T20:58:47Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T21:41:47Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:35:29Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:37:31Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:43:27Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:48:33Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:56:15Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T10:59:14Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T11:02:37Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:07:16Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:09:51Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:12:45Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:15:48Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:42:19Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:44:24Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:49:46Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T11:49:55Z
user: login: camilamacedo86
body: Hi @azych,  

Thank you for your time and explanations!  
Regarding: [Comment Reference](https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2710087085),  
I think it's best to discuss each point in its respective thread.  

### **About UX/Flags**  
For all cases:  
- Can we avoid `AND ... <other flag>`? It feels unnecessary—AND is already **implicit** when multiple flags are used, right?  
- Can we ensure all flags document their default behavior?  
  _(e.g., for `channels`: if not set, search in all)_  

**PS:** I’m fine with improving this later, but a small tweak for clarity wouldn’t hurt. It might also help us during the review process. If we can ensure that, it’d be great! That said, it’s a **NIT**—I won’t block the PR over it.  

### **Replacing "Operator" with "Cluster Extension"**  
Let’s leave this out for now.  

### **Final Review Status**  
I did another pass, and I think the last open point is `override-unset-with-current`.  
Its usage isn’t entirely clear to me—see [comment](https://github.com/operator-framework/kubectl-operator/pull/225/files#r1987122461).  

Beyond that, everything else seems addressed or will be handled later.  
So unless there’s more on this, Otherwise all fine `lgtm` ✅  

Thanks again for your time and input! 🚀  

state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T12:03:06Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T12:18:28Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:35:05Z
user: login: ankitathomas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T15:03:53Z
user: login: camilamacedo86
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T15:05:46Z
user: login: LalatenduMohanty
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T20:51:46Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T08:02:29Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T11:12:21Z
user: login: ankitathomas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:44:41Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:09:09Z
user: login: joelanford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T12:16:08Z
user: login: azych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:32:38Z
user: login: ankitathomas
body: /lgtm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:25:59Z

Review comments:
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: azych
body: alternatively we could use `set` to be more inline with `kubectl` behavior
created_at: 2025-03-05T11:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: do we need an option to provide a new serviceaccount name?
created_at: 2025-03-05T11:53:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: do we need a `Long` description here as well? I noticed some olmv0 commands have pretty comprehensive `Long` descriptions. 

I was thinking something similar (but shorter) to what kubectl explain provides for our CRDs could be useful?
created_at: 2025-03-05T11:55:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: azych
body: no cleanup is what olmv0 does as well. I think we agreed to ignore approach to waiting/cleanup here: https://github.com/operator-framework/kubectl-operator/pull/220#discussion_r1973356442, but still wanted to call it out here explicitly
created_at: 2025-03-05T11:57:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: ankitathomas
body: I think that's immutable, we shouldn't need to update that. It may be worth supporting label management though.
created_at: 2025-03-05T16:58:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: LalatenduMohanty
body: For OLM v1, we should move away from `operator` terminology.  We should call it clusterextension. @joelanford WDYT? 
created_at: 2025-03-05T21:05:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: LalatenduMohanty
body: > do we need a Long description here as well? 

+1 to long description
created_at: 2025-03-05T21:05:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > For OLM v1, we should move away from `operator` terminology. We should call it clusterextension. @joelanford WDYT?

IMO while the `operator olmv1` command prefix (olmv1 need to start with `kubectl operator olmv1` prefix) is still in, I think we should probably stick with using the `operator` terminology (olmv0 also did not work with `operator` CRDs), but if people think it's important to switch asap, I don't mind.

BTW. we had(?) this discussion internally on slack - maybe we shouldn't duplicate here?
created_at: 2025-03-06T08:41:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: LalatenduMohanty
body: > BTW. we had(?) this discussion internally on slack - maybe we shouldn't duplicate here?

I do not remember of any discussion we had on this. Also the intention is not to duplicate it.
created_at: 2025-03-06T13:46:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: shared the link internally
created_at: 2025-03-06T14:06:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: joelanford
body: This is kinda philosophical about what the CLI is trying to do. Is it just trying to send simple changes to a resource in kubernetes? Or is it trying to orchestrate an OLMv1-specific workflow?

In my head, it is the latter. But the hope is that implementing the latter is mostly: (1) make simple change, (2) tell user about progress of that change, (3) tell user whether it worked or not.

If we're shooting for the workflow style naming, my suggestion would be "upgrade" for extensions, but there isn't really an upgrade workflow for catalogs, so this gets into the whole bikeshed about the structure of the CLI subcommand tree.
created_at: 2025-03-06T14:53:05Z
author_association: MEMBER
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: joelanford
body: I think stick with `update` for now and we can decide on the structure later.
created_at: 2025-03-06T14:57:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: good idea, I'll add it the same way `catalog` creation has it now
created_at: 2025-03-07T08:08:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: camilamacedo86
body: Is this comment sorted out?
What should I look at? 
WDYT about closing this conversion to make it easier to follow up the PR?

created_at: 2025-03-07T11:16:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Is this comment sorted out?
Is the name picked by you folks used already? 
WDYT about closing this conversion to make it easier to follow up the PR? 

created_at: 2025-03-07T11:17:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: Can you please clarify why this comment is need?
If that is an important info, wdyt about add a comment in the code? 
created_at: 2025-03-07T11:18:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: ```suggestion
	fs.StringVar(&i.Version, "version", "", 
	"desired ClusterExtension version (single value or range) in SemVer format. This flag is used in combination with '--channels' to specify a version available within the selected channel(s).")

```

I’m not sure I fully understand the channels part—so the suggestion is to try make it more clear. 
Also, I do not think that we should use the term Operator, or is everything now focused on ClusterExtension. If we plan to support HelmCharts or other content types in the future, we might need to rethink this wording to avoid having to change everything again. :-(


created_at: 2025-03-07T11:20:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Could we add examples of how to use the command with **Cobra**?  

Providing a great **UX** is our ultimate goal, so we should strive to make the CLI tools as easy and intuitive as possible. And there's no better documentation than a well-structured **CLI helper**!  

Examples are usually very handy in guiding users.  

**WDYT?**  

created_at: 2025-03-07T11:24:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: How it works with multiple channels to be an array?
What does it means I pass a version with many channels?
Why it needs to be an array? 

I think we should have here 1 channel right?
singular and a string not an array.

```suggestion
	fs.StringVar(&i.Channel, "channel", "", 
	"specifies the channel from which the version should be selected. This flag works together with '--version' to determine the ClusterExtension version available in the specified channel. Default: look against all channels")

```
created_at: 2025-03-07T11:30:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: ```suggestion
	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", 
	"controls whether the upgrade path(s) defined in the catalog are enforced. OPTIONS: 'CatalogProvided', 'SelfCertified'. Default: 'CatalogProvided'.")



```
created_at: 2025-03-07T11:33:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: @azych 

@LalatenduMohanty added it here; https://github.com/operator-framework/kubectl-operator/pull/225/files#r1982184464

You may not be aware of this decision since it was made before you started contributing to the project. But **we have already discussed this in other places and reached a consensus.**  

We no longer use the term **"Operators"** instead we want **"ClusterExtension"**

The main reason for this change is our goals to support other types of content with OLMv1. As a result, we have agreed that everything should be referred to as **ClusterExtension**, and we should remain consistent with this terminology.  

> So, can you please 🙏 ensure that **all occurrences** of the term *Operators* are replaced with **`ClusterExtension`**?  

_PS.: Btw, to share: Say that everything that OLM manages or is produced by SDK or Kubebuilder is an Operator is either incorrect. It is possible have solutions that are not follow the Operator partner and still be in a bundle managed by OLM or done via the tools._ 

Thanks!  

created_at: 2025-03-07T12:21:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(catalogSrc *olmv1.CatalogSource) {
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: The use of an array above is intentional because if no value is set, we will check **all channels** by default.  

IMHO:  
- The flag should be a **singular string** (the user should specify only **one** channel).  
- We should use a **list only on the backend**.  
- The flag's description does not clarify that the channel can be empty or explain the default behavior when it is unset.  

Would it be possible to adjust this for better clarity and consistency?  

created_at: 2025-03-07T12:27:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Do we really want to allow the user to specify multiple channels?  
If so, why?  
What is the use case for this?  

Also, we need to explicitly state that the default behavior of this option is to check all channels, right?  


created_at: 2025-03-07T12:30:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: The function is missing validation for the CLI command.  

We need to check whether the provided values are valid, including their combinations, what is mandatory, and ensure we provide clear, helpful, and user-friendly feedback.  

**Example cases to validate:**  
- **`version`**:  
  - Is the version provided? Isn't it mandatory?  
  - If it's a range, is it a valid **SemVer** range?  
- **`upgrade-constraint-policy`**:  
  - Only two options are allowed—if set, we need to validate whether the provided option is valid.  

created_at: 2025-03-07T12:34:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Also, yep, we know that **OF**, **OLM**, and **kubectl-operator** all include "operator" in their names, but we cannot change that right now.  

We might revisit this in the future, and when that happens, we can migrate the code and reuse it accordingly.  

That said, the key takeaway here is to **make it clear that we are shifting to `ClusterExtension`**.  

### Why?  
- **Clarify that we handle `ClusterExtension`** (for both maintainers and users).  
- **Stick with the defined terminology** to ensure consistency.  
- **Reduce potential burden in the future** by aligning everything early on.  

created_at: 2025-03-07T12:47:20Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: ```suggestion
	i := v1action.NewClusterExtensionUpdate(cfg)
```
created_at: 2025-03-07T12:48:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: channels part are 1:1 channels you can see on our CRD - it has a good documentation, which I think should be sufficient if you want to know more

resolving following: https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2706623506
created_at: 2025-03-07T14:45:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: I agree, we are planning to add examples and detailed docs in the (near) future - https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2706623506 
created_at: 2025-03-07T14:45:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: * user is currently able to specify multiple channels in the `ClusterExtension` CRD, so no reason not to be able to do the same via CLI
* CRD has a good documentation on channels
* I think we shouldn't modify this to `channel` because it allows multiple channels
created_at: 2025-03-07T14:53:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: resolving because of: https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2706623506
created_at: 2025-03-07T14:54:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: we are already doing proper validation (with tests) on the actual action call
created_at: 2025-03-07T14:56:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: resolving as per: https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2706623506
created_at: 2025-03-07T14:56:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(catalogSrc *olmv1.CatalogSource) {
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
path: internal/pkg/v1/action/operator_update.go
user: login: azych
body: you raised this already in a different comment here: https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984908346

* I think we should be able to set multiple channels if the CRD allows us to do so
* a short information about a default behavior when unset makes sense - added
created_at: 2025-03-07T15:01:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: with `one of:` I was following what olmv0 used in some of their commands, I think we can revisit that when we dive deeper into docs in the (near) future, context: https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984908346
created_at: 2025-03-07T15:05:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: azych
body: > Is this comment sorted out? What should I look at? WDYT about closing this conversion to make it easier to follow up the PR?

When you say it would be easier to follow up the PR what do you mean exactly? I don't want to start a new long thread here about how people approach reviewing PRs, but I'm curious.

If I start reviewing a PR I would look at both resolved and unresolved previous comments and If I saw situation like in this thread, I'd assume author has agreed with moving on with `update`. If I had any additional valuable input about that, I'd still add a comment here, so I'm not sure what the difference is.
created_at: 2025-03-07T15:40:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > Is this comment sorted out? Is the name picked by you folks used already? WDYT about closing this conversion to make it easier to follow up the PR?

well, it wasn't sorted out when you commented because I didin't yet make the code change.
created_at: 2025-03-07T15:42:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: azych
body: > Can you please clarify why this comment is need?
If that is an important info, wdyt about add a comment in the code?

I thought it would be useful to reviewers, why?
created_at: 2025-03-07T15:43:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: I'm just trying to understand what you mean without getting pulled into other threads and multiple links since you have the full context.

So, wdyt adding a brief comment in the code with a simple overview/explanation (KISS) to make it clear for everyone if that is important?
created_at: 2025-03-07T16:26:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: What do you mean with?
The examples will not be added to this PR?
Do we have a PR only to do that? 
created_at: 2025-03-07T16:27:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(catalogSrc *olmv1.CatalogSource) {
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: We can keep it centralized there.
created_at: 2025-03-07T16:32:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: +1 for @akihikokuroda thought about SA
I do not think we should allow a user to pass the SA for upgrade a ClusterExtension
What is the use case?
Why would it be required? 

However, passing the `catalog` shows good because what happens if we have the same solution in different catalogs? Also, if we do not inform that, and we have the scenario, what happens? 
created_at: 2025-03-07T16:56:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: 1. How does it work when I pass two channels? What happens in that case? Also, could you clarify why and when I should pass more than one channel?
2. Could we avoid using `AND operation with ...?` Instead, can we try to use a more common ux friendly description, similar to other tools, operator-controller flags, etc.?"

created_at: 2025-03-07T17:04:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Can we please try to use a more common ux friendly description, similar to other tools, operator-controller flags, etc. before get the PR merged?

created_at: 2025-03-07T17:06:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Sorry, can you please point out? Do you mean here: https://github.com/operator-framework/kubectl-operator/pull/225/files#diff-93c2b75d51a3e1bd83720cd237f0769f06dcc8f0748d51542ab4d018aa1f1fc5R63-R67

What I mean here is validate the input like : https://github.com/operator-framework/operator-sdk/blob/b54b08448bc9ea0931c1fb05cc825b7def085b5b/internal/cmd/operator-sdk/pkgmantobundle/cmd.go#L131C13-L131C21 

### TL'DR Example

Before starting to do something, validate the input to avoid failures in the execution because it is missing a mandatory field or has wrong values. 

```go
const (
    PolicyCatalogProvided = "CatalogProvided"
    PolicySelfCertified   = "SelfCertified"
)

func validate(args) error {
    validPolicies := map[string]bool{
        PolicyCatalogProvided: true,
        PolicySelfCertified:   true,
    }

   if _, isValid := validPolicies[policy]; !isValid {
        return fmt.Errorf("invalid value for --upgrade-constraint-policy: %s. Allowed values are: %s, %s",
            policy, PolicyCatalogProvided, PolicySelfCertified)
    }

    // If channel is specified, version must also be provided
    if channel != "" && version == "" {
        return errors.New("if a channel is specified, a version must also be provided")
    }
    
    // Is valid range semVer check 
....
```

```go

    cmd := &cobra.Command{
       ...
        PreRunE: func(cmd *cobra.Command, args []string) error {
            return validate(args)
        },
        ...
    }
```
created_at: 2025-03-07T18:10:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: What flags do we have in the ClusterExtension that we can override? 
Could you please help me understand what the use case is here? Provide an example? 
created_at: 2025-03-07T18:19:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,119 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"maps"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+)
+
+type updateClient interface {
+	updater
+	getter
+}
+
+type OperatorUpdate struct {
+	client updateClient
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	Labels                  map[string]string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(client updateClient) *OperatorUpdate {
+	return &OperatorUpdate{
+		client: client,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext, constraintPolicy) {
+		return nil, ErrNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.SetLabels(ou.Labels)
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(ext olmv1.ClusterExtension) {
+	catalogSrc := ext.Spec.Source.Catalog
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
+		}
+		if ou.UpgradeConstraintPolicy == "" {
+			ou.UpgradeConstraintPolicy = string(catalogSrc.UpgradeConstraintPolicy)
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: What exactly means:

> fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")


What does `whether the upgrade path(s) defined in the catalog are enforced` mean?
Could you please provide an example or use case to clarify what it means? 
What is the difference? 



created_at: 2025-03-07T20:58:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: I followed the thread, and checked the links. WDYT about adding a brief comment at the top of the method for clarity, avoiding the need to follow links. This may help future maintainers and make it easier for reviewers? 

```
// The CLI should ideally provide status updates to indicate progress or recoverable issues,  
// but for now, maintaining the current wait behavior makes sense. A broader rethink of wait logic  
// across the CLI may be needed, potentially with a flag to control waiting behavior.  
// This relates to handling the `Progressing` condition and retry limits in the API.  
// More info: https://github.com/operator-framework/operator-controller/issues/1440
```

created_at: 2025-03-07T21:41:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > However, passing the catalog shows good because what happens if we have the same solution in different catalogs? Also, if we do not inform that, and we have the scenario, what happens?

You're asking to add a `--catalog` flag for the user to restrict the edges further?
I'm not opposed if people think we need that functionality _now_. If we want full-fledged filtering via `--selector`, I think this should happen as a separate effort (and PR, see also: https://github.com/operator-framework/kubectl-operator/pull/219#discussion_r1978898067).
created_at: 2025-03-10T10:35:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/blang/semver/v4"
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+type OperatorUpdate struct {
+	config *action.Configuration
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
+	return &OperatorUpdate{
+		config: cfg,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext.Spec.Source.Catalog)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
+		return nil, errNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.config.Client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
path: internal/pkg/v1/action/operator_update.go
user: login: azych
body: At this point, I wouldn't say we know what the `ideal` scenario here is (https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2710087085), but I don't think it needs to be a code comment. I'll still add it on the epic issue to keep track when we get to those discussions.
Hope that's ok.
created_at: 2025-03-10T10:37:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > Could we avoid using AND operation with ...? Instead, can we try to use a more common ux friendly description, similar to other tools, operator-controller flags, etc.?"

This was taken from our official v1.2.0 operator-controller CRD documentation - `This constraint is an AND operation with the version field`
Let me know if you consider this a blocker given this information and https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2710087085
created_at: 2025-03-10T10:43:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: just for the record, you're proposing to change:
```
One of CatalogProvided|SelfCertified), Default: CatalogProvided"
```

to:
```
OPTIONS: 'CatalogProvided', 'SelfCertified'. Default: 'CatalogProvided'."
```

I am sorry, but I'd rather follow olmv0 pattern (meaning `One of, |`) than an arbitrary and unspecified `common ux friendly description`. I'd ask you to respect that and not unresolve the comment - if you feel strongly about this, let's revisit when we'll be reviewing the documentation as a whole before we get to RC with those new commands (https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2710087085).
Hope that's fair.
created_at: 2025-03-10T10:48:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > Sorry, can you please point out? Do you mean here: https://github.com/operator-framework/kubectl-operator/pull/225/files#diff-93c2b75d51a3e1bd83720cd237f0769f06dcc8f0748d51542ab4d018aa1f1fc5R63-R67

yes, validation happens within `Run`, the same place where the block of code you linked to is

> What I mean here is validate the input like : https://github.com/operator-framework/operator-sdk/blob/b54b08448bc9ea0931c1fb05cc825b7def085b5b/internal/cmd/operator-sdk/pkgmantobundle/cmd.go#L131C13-L131C21

problem with this is that empty values are still valid both from the POV of our schema (channels, version) as well as assuming existing value when not provided (constraint policy), so it makes sense to validate it in `Run` rather than `PreRun`.
created_at: 2025-03-10T10:56:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: those are the CLI flag values above the line you commented and the use case is for example: "if I don't explicitly provide a value, that means I don't want to change it in respect to current state of the object, so that I don't have to type that much"

Bu now that I think of it, we can differentiate with pointers in implementation, so it might not be needed after all.
created_at: 2025-03-10T10:59:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,119 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"maps"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+)
+
+type updateClient interface {
+	updater
+	getter
+}
+
+type OperatorUpdate struct {
+	client updateClient
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	Labels                  map[string]string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(client updateClient) *OperatorUpdate {
+	return &OperatorUpdate{
+		client: client,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext, constraintPolicy) {
+		return nil, ErrNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.SetLabels(ou.Labels)
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(ext olmv1.ClusterExtension) {
+	catalogSrc := ext.Spec.Source.Catalog
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
+		}
+		if ou.UpgradeConstraintPolicy == "" {
+			ou.UpgradeConstraintPolicy = string(catalogSrc.UpgradeConstraintPolicy)
path: internal/pkg/v1/action/operator_update.go
user: login: azych
body: > What does whether the upgrade path(s) defined in the catalog are enforced mean?
Could you please provide an example or use case to clarify what it means?
What is the difference?

this was taken verbatim from our CRD documentation. I'd rather not paste it here, because it's not brief, but hopefully this will be satisfactory for you to move forward? 

Please remember that we are planning on adjusting the docs once we have all commands (not that I think we should do that necessarily if it comes from our CRD docs)
created_at: 2025-03-10T11:02:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,119 @@
+package action
+
+import (
+	"context"
+	"fmt"
+	"maps"
+	"slices"
+	"time"
+
+	"github.com/blang/semver/v4"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	olmv1 "github.com/operator-framework/operator-controller/api/v1"
+)
+
+type updateClient interface {
+	updater
+	getter
+}
+
+type OperatorUpdate struct {
+	client updateClient
+
+	Package string
+
+	Version                 string
+	Channels                []string
+	UpgradeConstraintPolicy string
+	Labels                  map[string]string
+	OverrideUnset           bool
+
+	CleanupTimeout time.Duration
+
+	Logf func(string, ...interface{})
+}
+
+func NewOperatorUpdate(client updateClient) *OperatorUpdate {
+	return &OperatorUpdate{
+		client: client,
+		Logf:   func(string, ...interface{}) {},
+	}
+}
+
+func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
+	var ext olmv1.ClusterExtension
+
+	opKey := types.NamespacedName{Name: ou.Package}
+	if err := ou.client.Get(ctx, opKey, &ext); err != nil {
+		return nil, err
+	}
+
+	if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
+		return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
+	}
+
+	ou.setDefaults(ext)
+	constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
+	if !ou.needsUpdate(ext, constraintPolicy) {
+		return nil, ErrNoChange
+	}
+
+	if ou.Version != "" {
+		if _, err := semver.ParseRange(ou.Version); err != nil {
+			return nil, fmt.Errorf("failed parsing version: %w", err)
+		}
+	}
+
+	ext.SetLabels(ou.Labels)
+	ext.Spec.Source.Catalog.Version = ou.Version
+	ext.Spec.Source.Catalog.Channels = ou.Channels
+	ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
+	if err := ou.client.Update(ctx, &ext); err != nil {
+		return nil, err
+	}
+
+	if err := waitUntilOperatorStatusCondition(ctx, ou.client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
+		return nil, fmt.Errorf("timed out waiting for operator: %w", err)
+	}
+
+	return &ext, nil
+}
+
+func (ou *OperatorUpdate) setDefaults(ext olmv1.ClusterExtension) {
+	catalogSrc := ext.Spec.Source.Catalog
+	if ou.OverrideUnset {
+		if ou.Version == "" {
+			ou.Version = catalogSrc.Version
+		}
+		if len(ou.Channels) == 0 {
+			ou.Channels = catalogSrc.Channels
+		}
+		if ou.UpgradeConstraintPolicy == "" {
+			ou.UpgradeConstraintPolicy = string(catalogSrc.UpgradeConstraintPolicy)
path: internal/pkg/v1/action/operator_update.go
user: login: camilamacedo86
body: I found the explaination here; https://operator-framework.github.io/operator-controller/api-reference/operator-controller-api-reference/#catalogfilter

Cool, 
created_at: 2025-03-10T11:07:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Yes, that is my suggestion.
I think that is how we have it controller-tools as well, but it does not seem very significant as well and we have it in OLMv0 I am fine,
created_at: 2025-03-10T11:09:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: `AND operation with ` for the flag description seems not offend used right?
But that is a NIT for sure
created_at: 2025-03-10T11:12:45Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: I think I found the asnwer: If multiple channels are specified, I understand now it means  (from [API reference](https://operator-framework.github.io/operator-controller/api-reference/operator-controller-api-reference/#catalogfilter)) : 
  **"Only upgrade if the version is found in this set of channels; otherwise, ignore."** 

- So clarify:
a) default behaviour when not informed
b) Avoid `AND operation with version` ( seems wirdo for flag desc right )
c) What does with have many

I think would be nice
created_at: 2025-03-10T11:15:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: So, we set the flag value in the command, and then, in the same command, do we override?
I am unsure if I understand how a user would use it and what circumstances. 
created_at: 2025-03-10T11:42:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: After thinking more, I think it's OK, do not add the catalog either WHY?
- We will upgrade what is installed, correct?
- So should be from the source used previously, not the other. 
- Either we cannot have 2 instances of the same solution so I think we can ignore this idea of catalog 👍 
created_at: 2025-03-10T11:44:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: I see. I am unsure if we are validating everything, but I think I am OK.
I can see that we are doing the check before the run update, which addresses my biggest concern. 👍 
created_at: 2025-03-10T11:49:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
path: internal/cmd/internal/olmv1/operator_update.go
user: login: camilamacedo86
body: Marking as solved !
created_at: 2025-03-10T11:49:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: With the current implementation:
1. there is a clusterextension 'some_name', let's say with `SelfCertified` upgrade constraint policy already on cluster
2. CLI user runs the command to update passing just `operator update some_name --version=1.0.1`
3. without override flag enabled, update would take the default value (which is `CatalogProvided`) even though the flag was not set
4. with the override flag enabled, update would assume (correctly from my POV) that user does not want any changes to anything else on the existing clusterextension other than 'version'

I think we should be able to skip having this override flag altogether and take the same meaning from existance/non existance of a specific flag that is passed:
1.  with CLI called with `operator update some_name --version=1.0.1`
2. *upgradeConstraintPolicy pointer would be empty (flag was not set), which would mean we wouldn't be doing any updating of upgrade constraint policy
3. in case --upgradeConstraintPolicy was passed at all and had value, we'd use this value
4. in case --upgradeConstraintPolicy was passed but had an empty value, we'd assume default (CatalogProvided)

WDYT? Does that make sense?
created_at: 2025-03-10T12:18:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: after trying this out, it doesn't seem the pointer approach is going to work here. Flags require an allocated memory upfront for binding.
So this leaves us with `override-unset-with-current` if we want to keep this behavior, unless people have other ideas.
created_at: 2025-03-10T13:35:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: ankitathomas
body: @azych , so in essence, this is to ensure we have some way of unsetting optional fields within upgradeConstraints, which includes the `upgradeConstraintPolicy`, `channels` and `version` as of now. 

It may be surprising to the user that something they set within the constraints gets overridden by the cli by default when they haven't even specified any arguments - we may want to switch default behavior here. Does it make sense to ignore unset flags by default, and only unset the values on the clusterExtension if a flag is provided? (i.e, switching from `--override-unset-with-current` to `--unset-empty` or something similar)
created_at: 2025-03-10T14:38:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: ankitathomas
body: I think it's fair to treat this thread as resolved for now for the purpose of the review.
created_at: 2025-03-10T14:57:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: ankitathomas
body: I think the only way we have of specifying a catalog to install from within the clusterExtension CRD is through an optional labelSelector. That being said, I think it's best to support modifying at least that for now, even if we don't support specifying individual catalogs yet.
created_at: 2025-03-10T15:03:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
 	}
 	createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
 
+	updateCmd := &cobra.Command{
+		Use:   "update",
path: internal/cmd/olmv1.go
user: login: camilamacedo86
body: Thank you for the clarification
created_at: 2025-03-10T15:05:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: LalatenduMohanty
body: Yeah, as per API we can provide multiple channels. if the respective version (to which the cluster extension is trying to update) not available then we will error-out appropriately. If the current error is not a good one then we should fix it in operator-controller code.  
created_at: 2025-03-10T20:51:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: > ensure we have some way of unsetting optional fields

So this flag, if enabled, will currently only override unset values with values already on the object we're updating. Because constraint policy comes with a default value if not set, it could look otherwise I guess.
created_at: 2025-03-11T08:02:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,40 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced, one of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
+}
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: I added full selector support, courtesy of finding the `metav1.ParseToLabelSelector`

Unit test case was not extended to show it, because we'll be switching to using `WithInterceptorFuncs` anyway in https://github.com/operator-framework/kubectl-operator/pull/226/files

I added additional manual testing output in the walkthrough though
created_at: 2025-03-11T11:12:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: ankitathomas
body: What I'm worried about is our default behavior with the catalog source information.

If for an operator with catalog constraints like so:
```
$ kubectl get clusterextensions -o yaml | yq eval '.items[].spec.source' -
catalog:
  channels:
    - alpha
    - delta
  packageName: test-operator
  upgradeConstraintPolicy: CatalogProvided
sourceType: Catalog
```

running the following drops all fields except the one specified like so:
```
$ ./kubectl-operator olmv1 update operator test-operator --upgrade-constraint-policy=SelfCertified
$ kubectl get clusterextensions -o yaml | yq eval '.items[].spec.source' -
catalog:
  packageName: test-operator
  upgradeConstraintPolicy: SelfCertified
sourceType: Catalog
```

That may be surprising default behavior. 

It's doubly surprising when running the command with no flags drops fields on the clusterextension.
 ```
$ ./kubectl-operator olmv1 update operator test-operator
$ kubectl get clusterextensions -o yaml | yq eval '.items[].spec.source' -
catalog:
  packageName: test-operator
  upgradeConstraintPolicy: CatalogProvided
sourceType: Catalog
 ```

I'd expect default behavior to be more in line with how `--override-unset-with-current` acts, where only the field I specify gets modified:
```
$ ./kubectl-operator olmv1 update operator test-operator --upgrade-constraint-policy=SelfCertified --override-unset-with-current
$ kubectl get clusterextensions -o yaml | yq eval '.items[].spec.source' -
catalog:
  channels:
    - beta
    - alpha
  packageName: test-operator
  upgradeConstraintPolicy: SelfCertified
  version: 0.6.0
sourceType: Catalog
 ```

 
Possibly, all this takes should be setting `OverrideUnset` to true by default.
 
created_at: 2025-03-11T14:44:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+package olmv1
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+
+	"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
+	v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
+	"github.com/operator-framework/kubectl-operator/pkg/action"
+)
+
+// NewOperatorUpdateCmd allows updating a selected operator
+func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
+	i := v1action.NewOperatorUpdate(cfg.Client)
+	i.Logf = log.Printf
+
+	cmd := &cobra.Command{
+		Use:   "operator <operator>",
+		Short: "Update an operator",
+		Args:  cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			i.Package = args[0]
+			_, err := i.Run(cmd.Context())
+			if err != nil {
+				log.Fatalf("failed to update operator: %v", err)
+			}
+			log.Printf("operator %q updated", i.Package)
+		},
+	}
+	bindOperatorUpdateFlags(cmd.Flags(), i)
+
+	return cmd
+}
+
+func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
+	fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
+	fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version. Empty list means all available channels will be taken into consideration.")
+	fs.StringVar(&i.UpgradeConstraintPolicy, "upgrade-constraint-policy", "", "controls whether the upgrade path(s) defined in the catalog are enforced. One of CatalogProvided|SelfCertified), Default: CatalogProvided")
+	fs.StringToStringVar(&i.Labels, "labels", map[string]string{}, "labels that will be set on the operator")
+	fs.BoolVar(&i.OverrideUnset, "override-unset-with-current", false, "when enabled, any unset flag value will be overridden with value already set in current operator")
path: internal/cmd/internal/olmv1/operator_update.go
user: login: azych
body: I agree, let's not be surprising like that
created_at: 2025-03-12T08:09:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,3 +65,69 @@ func newClusterCatalog(name string) *olmv1.ClusterCatalog {
 		ObjectMeta: metav1.ObjectMeta{Name: name},
 	}
 }
+
+type extensionOpt func(*olmv1.ClusterExtension)
+
+func withVersion(version string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.Catalog.Version = version
+	}
+}
+
+func withSourceType(sourceType string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.SourceType = sourceType
+	}
+}
+
+// nolint: unparam
path: internal/pkg/v1/action/action_suite_test.go
user: login: joelanford
body: Which lint error is this masking?
created_at: 2025-03-12T12:16:08Z
author_association: MEMBER
diff_hunk: @@ -63,3 +65,69 @@ func newClusterCatalog(name string) *olmv1.ClusterCatalog {
 		ObjectMeta: metav1.ObjectMeta{Name: name},
 	}
 }
+
+type extensionOpt func(*olmv1.ClusterExtension)
+
+func withVersion(version string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.Catalog.Version = version
+	}
+}
+
+func withSourceType(sourceType string) extensionOpt {
+	return func(ext *olmv1.ClusterExtension) {
+		ext.Spec.Source.SourceType = sourceType
+	}
+}
+
+// nolint: unparam
path: internal/pkg/v1/action/action_suite_test.go
user: login: azych
body: internal/pkg/v1/action/action_suite_test.go:83:27  unparam  `withConstraintPolicy` - `policy` always receives `string(olmv1.UpgradeConstraintPolicyCatalogProvided)` (`"CatalogProvided"`)

I don't think this needs to be 'fixed', hence `nolint`. `withConstraintPolicy` is used only to build test operators (extension) in tests. We do have test cases where we check setting a different value, but the base remains the same.
created_at: 2025-03-12T14:32:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: azych
created_at: 2025-03-07T14:43:21Z
body: There was an internal meeting yesterday regarding current CLI efforts in kubectl-operator. The gist of it was:
* we focus on getting the core functionality in
* detailed docs, potential syntax and terminology changes (such as `operator` -> `extension` (or anything else) will be deferred until this goes to dev preview

This is the epic that reflects the roadmap: https://github.com/operator-framework/operator-controller/issues/1424  
author_association: CONTRIBUTOR
user: login: azych
created_at: 2025-03-10T10:21:46Z
body: @camilamacedo86 I'll group some of the responses to your comments to help with the organization at least a little.

**Multiple Channels** (raised in: https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1985408083, https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984979085, https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984975762)

* As I mentioned in responses (eg. https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1985202259), our CRD allows multiple channels to be set and the documentation does not mention any restrictions (that you can provide either no channels at all or a single one) - this is the first line of our docs (docs are fairly long, so I won't be pasting them here in full): ```  channels	<[]string>
    channels is an optional reference to a set of channels belonging to
    the package specified in the packageName field.```
* If you have a strong opinion about this (and I think you do, since you unresolved that comment) I would suggest maybe raising this an issue in the team/on the operator-controller, which owns the CRD. This way, if it needs fixing (should not be an array, but a single channel with empty value meaning 'all' channels) it will be fixed at the source first. Hopefully, this will unblock this PR from this discussion.

**Documentation changes/questions** (raised in: https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984896802, https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984908346, https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1984901062, https://github.com/operator-framework/kubectl-operator/pull/225#discussion_r1985704935)

* flags documentation (help messages) are either taken from our CRD documentation (and shortened) or are created from scratch when there is no direct relation. Flags that you're raising issues about come mostly from our CRD docs.
* patterns (like with `One of: |` come from current olmv0 commands that are defined in this repo
* when it comes to improvements, the idea was to holistically review documentation and extend it to be user-facing at a later time, where we have all the commands ready and we have an RC (what I was trying to say here: https://github.com/operator-framework/kubectl-operator/pull/225#issuecomment-2706623506, sorry if it wasn't too clear) to avoid lenghty discussions over things like:
* Should `One of CatalogProvided|SelfCertified), Default: CatalogProvided"` be `OPTIONS: 'CatalogProvided', 'SelfCertified'. Default: 'CatalogProvided'."` up to the point of unresolving comments.

**General comments/notes**

* new commands syntax/structure comes from the first review, where we discussed commands should provide `kubectl`-like experience (see: https://github.com/operator-framework/kubectl-operator/pull/218, and especially https://github.com/operator-framework/kubectl-operator/pull/218#issuecomment-2668683637)
* documentation (short help phrases on each command) are either taken from existing CRD docs or added from scratch and their patterns taken from olmv0 command docs patterns. I know I mentioned it above, but still wanted to call out here that this has been the approach so far
* we've decided to use 'operator' instead of potentially switching to 'extension' for now

In the (near) future (https://github.com/operator-framework/operator-controller/issues/1851, https://github.com/operator-framework/operator-controller/issues/1766, https://github.com/operator-framework/operator-controller/issues/1850):
* with olmv0 deprecation we have several options on how to approach the separation - new project, v1/, etc. This can (and most likely will come) with architecture changes
* with that deprecation we will possibly be looking at getting rid/changing of the `kubectl operator olmv1` prefix that currently needs to be used for the new commands
* we agreed to review and extend documentation towards the end of the commands efforts
* the commands we're adding right now are meant to provide a good base for extending with new features later
* because of the last point, with things like 'how do we approach cleanup', 'how do we approach waiting for new object status (if at all) we agreed to defer those discussions and for now chose to go with whatever olmv0 was doing (https://github.com/operator-framework/kubectl-operator/pull/220#discussion_r1973356442)

The gist of those future plans is that with the new commands we look to come up with a good base functionality that we can later adjust and extend if we see fit (eg. someone makes a compelling argument why syntax should not follow `kubectl` behavior) and we wanted to avoid blocking this basic functionality over what could (would) be lengthy discussions and research.


Side note: sorry for this wall of text with multiple links, but I thought it was beneficial at this point to centralize some topics and provide a common background and reasoning for some of things we are doing now and have been doing in previous commands.
author_association: CONTRIBUTOR
user: login: LalatenduMohanty
created_at: 2025-03-10T19:02:55Z
body: For some reason I am not able to checkout the PR to test locally using github CLI

```
$ gh pr checkout 225
From https://github.com/operator-framework/kubectl-operator
 ! [rejected]        refs/pull/225/head -> olmv1-update-operator  (non-fast-forward)
failed to run git: exit status 1
```
May be re-basing it will help 
author_association: MEMBER
user: login: azych
created_at: 2025-03-11T07:54:23Z
body: > For some reason I am not able to checkout the PR to test locally using github CLI
> 
> ```
> $ gh pr checkout 225
> From https://github.com/operator-framework/kubectl-operator
>  ! [rejected]        refs/pull/225/head -> olmv1-update-operator  (non-fast-forward)
> failed to run git: exit status 1
> ```
> 
> May be re-basing it will help

This is already up to date with the main branch.

BTW. Wouldn't you say it would be better to keep issues like out of PR comments? We do have the team slack and IMO it would be best to keep the comments cleaner.
author_association: CONTRIBUTOR
user: login: LalatenduMohanty
created_at: 2025-03-11T22:08:08Z
body: > BTW. Wouldn't you say it would be better to keep issues like out of PR comments? We do have the team slack and IMO it would be best to keep the comments cleaner.

I understand your sentiment, my intent is to keep all comment in public domain as much as possible. As we are a community project. I can use use #olm-dev channel  for this if you prefer instead of PR comment. Personally I do not see an issue in PR comment vs slack message. 
author_association: MEMBER
user: login: azych
created_at: 2025-03-12T08:24:55Z
body: > > BTW. Wouldn't you say it would be better to keep issues like out of PR comments? We do have the team slack and IMO it would be best to keep the comments cleaner.
> 
> I understand your sentiment, my intent is to keep all comment in public domain as much as possible. As we are a community project. I can use use #olm-dev channel for this if you prefer instead of PR comment. Personally I do not see an issue in PR comment vs slack message.

yes, regardless of whether the project is open- or closed- source, in my opinion issues with arbitrary third party tools (like `gh` not being able to check out a branch) that have no real relation to the PR being reviewed should be kept outside of (root) comments for such PR. 
Generally, I think we should take care with comments so they won't become messy and difficult to read and follow. To me that includes things like keeping them on topic and avoiding scattered discussions about the same issue for example.
author_association: CONTRIBUTOR
user: login: azych
created_at: 2025-03-12T10:28:22Z
body: we now have full `--selector` support and the default behavior of `--ignore-unset` (new name) was reversed.
I've also introduced `parsedSelector` to the internal command structure and fixed the selector comparison issues I came across.
author_association: CONTRIBUTOR