Skip to content

Commit 2e125d6

Browse files
committed
Add command to update olmv1 operator
Signed-off-by: Artur Zych <[email protected]>
1 parent adb5f78 commit 2e125d6

File tree

7 files changed

+207
-30
lines changed

7 files changed

+207
-30
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/pflag"
6+
7+
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
8+
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
9+
"github.com/operator-framework/kubectl-operator/pkg/action"
10+
)
11+
12+
// NewOperatorUpdateCmd allows updating a selected operator
13+
func NewOperatorUpdateCmd(cfg *action.Configuration) *cobra.Command {
14+
i := v1action.NewOperatorUpdate(cfg)
15+
i.Logf = log.Printf
16+
17+
cmd := &cobra.Command{
18+
Use: "operator <operator>",
19+
Short: "Update an operator",
20+
Args: cobra.ExactArgs(1),
21+
Run: func(cmd *cobra.Command, args []string) {
22+
i.Package = args[0]
23+
_, err := i.Run(cmd.Context())
24+
if err != nil {
25+
log.Fatalf("failed to update operator: %v", err)
26+
}
27+
log.Printf("operator %q updated", i.Package)
28+
},
29+
}
30+
bindOperatorUpdateFlags(cmd.Flags(), i)
31+
32+
return cmd
33+
}
34+
35+
func bindOperatorUpdateFlags(fs *pflag.FlagSet, i *v1action.OperatorUpdate) {
36+
fs.StringVar(&i.Version, "version", "", "desired operator version (single or range) in semver format. AND operation with channels")
37+
fs.StringArrayVar(&i.Channels, "channels", []string{}, "desired channels for operator versions. AND operation with version")
38+
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")
39+
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")
40+
}

internal/cmd/olmv1.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,21 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
3131
}
3232
createCmd.AddCommand(olmv1.NewCatalogCreateCmd(cfg))
3333

34+
updateCmd := &cobra.Command{
35+
Use: "update",
36+
Short: "Update an existing resource",
37+
Long: "Update an existing resource",
38+
}
39+
updateCmd.AddCommand(
40+
olmv1.NewOperatorUpdateCmd(cfg),
41+
)
42+
3443
cmd.AddCommand(
3544
olmv1.NewOperatorInstallCmd(cfg),
3645
olmv1.NewOperatorUninstallCmd(cfg),
3746
getCmd,
3847
createCmd,
48+
updateCmd,
3949
)
4050

4151
return cmd

internal/pkg/v1/action/errors.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package action
2+
3+
import "errors"
4+
5+
var (
6+
errNoChange = errors.New("no changes detected - operator already in desired state")
7+
)

internal/pkg/v1/action/helpers.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package action
22

33
import (
44
"context"
5+
"fmt"
56
"slices"
7+
"strings"
68
"time"
79

810
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -12,6 +14,7 @@ import (
1214
"sigs.k8s.io/controller-runtime/pkg/client"
1315

1416
olmv1catalogd "github.com/operator-framework/catalogd/api/v1"
17+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1518
)
1619

1720
const pollInterval = 250 * time.Millisecond
@@ -45,6 +48,28 @@ func waitUntilCatalogStatusCondition(
4548
})
4649
}
4750

51+
func waitUntilOperatorStatusCondition(
52+
ctx context.Context,
53+
cl getter,
54+
operator *olmv1.ClusterExtension,
55+
conditionType string,
56+
conditionStatus metav1.ConditionStatus,
57+
) error {
58+
opKey := objectKeyForObject(operator)
59+
return wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
60+
if err := cl.Get(conditionCtx, opKey, operator); err != nil {
61+
return false, err
62+
}
63+
64+
if slices.ContainsFunc(operator.Status.Conditions, func(cond metav1.Condition) bool {
65+
return cond.Type == conditionType && cond.Status == conditionStatus
66+
}) {
67+
return true, nil
68+
}
69+
return false, nil
70+
})
71+
}
72+
4873
func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) error {
4974
ctx, cancel := context.WithTimeout(context.Background(), timeout)
5075
defer cancel()
@@ -55,3 +80,22 @@ func deleteWithTimeout(cl deleter, obj client.Object, timeout time.Duration) err
5580

5681
return nil
5782
}
83+
84+
func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error {
85+
for _, obj := range objs {
86+
obj := obj
87+
lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
88+
key := objectKeyForObject(obj)
89+
if err := wait.PollUntilContextCancel(ctx, pollInterval, true, func(conditionCtx context.Context) (bool, error) {
90+
if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) {
91+
return true, nil
92+
} else if err != nil {
93+
return false, err
94+
}
95+
return false, nil
96+
}); err != nil {
97+
return fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err)
98+
}
99+
}
100+
return nil
101+
}

internal/pkg/v1/action/operator.go

Lines changed: 0 additions & 9 deletions
This file was deleted.

internal/pkg/v1/action/operator_uninstall.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77

88
apierrors "k8s.io/apimachinery/pkg/api/errors"
99
"k8s.io/apimachinery/pkg/types"
10-
"k8s.io/apimachinery/pkg/util/wait"
11-
"sigs.k8s.io/controller-runtime/pkg/client"
1210

1311
olmv1 "github.com/operator-framework/operator-controller/api/v1"
1412

@@ -42,22 +40,3 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
4240
}
4341
return waitForDeletion(ctx, u.config.Client, op)
4442
}
45-
46-
func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error {
47-
for _, obj := range objs {
48-
obj := obj
49-
lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
50-
key := objectKeyForObject(obj)
51-
if err := wait.PollUntilContextCancel(ctx, pollTimeout, true, func(conditionCtx context.Context) (bool, error) {
52-
if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) {
53-
return true, nil
54-
} else if err != nil {
55-
return false, err
56-
}
57-
return false, nil
58-
}); err != nil {
59-
return fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err)
60-
}
61-
}
62-
return nil
63-
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package action
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
"time"
8+
9+
"github.com/blang/semver/v4"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
12+
13+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
14+
15+
"github.com/operator-framework/kubectl-operator/pkg/action"
16+
)
17+
18+
type OperatorUpdate struct {
19+
config *action.Configuration
20+
21+
Package string
22+
23+
Version string
24+
Channels []string
25+
UpgradeConstraintPolicy string
26+
OverrideUnset bool
27+
28+
CleanupTimeout time.Duration
29+
30+
Logf func(string, ...interface{})
31+
}
32+
33+
func NewOperatorUpdate(cfg *action.Configuration) *OperatorUpdate {
34+
return &OperatorUpdate{
35+
config: cfg,
36+
Logf: func(string, ...interface{}) {},
37+
}
38+
}
39+
40+
func (ou *OperatorUpdate) Run(ctx context.Context) (*olmv1.ClusterExtension, error) {
41+
var ext olmv1.ClusterExtension
42+
43+
opKey := types.NamespacedName{Name: ou.Package}
44+
if err := ou.config.Client.Get(ctx, opKey, &ext); err != nil {
45+
return nil, err
46+
}
47+
48+
if ext.Spec.Source.SourceType != olmv1.SourceTypeCatalog {
49+
return nil, fmt.Errorf("unrecognized source type: %q", ext.Spec.Source.SourceType)
50+
}
51+
52+
ou.setDefaults(ext.Spec.Source.Catalog)
53+
constraintPolicy := olmv1.UpgradeConstraintPolicy(ou.UpgradeConstraintPolicy)
54+
if !ou.needsUpdate(ext.Spec.Source.Catalog, constraintPolicy) {
55+
return nil, errNoChange
56+
}
57+
58+
if ou.Version != "" {
59+
if _, err := semver.ParseRange(ou.Version); err != nil {
60+
return nil, fmt.Errorf("failed parsing version: %w", err)
61+
}
62+
}
63+
64+
ext.Spec.Source.Catalog.Version = ou.Version
65+
ext.Spec.Source.Catalog.Channels = ou.Channels
66+
ext.Spec.Source.Catalog.UpgradeConstraintPolicy = constraintPolicy
67+
if err := ou.config.Client.Update(ctx, &ext); err != nil {
68+
return nil, err
69+
}
70+
71+
if err := waitUntilOperatorStatusCondition(ctx, ou.config.Client, &ext, olmv1.TypeInstalled, metav1.ConditionTrue); err != nil {
72+
return nil, fmt.Errorf("timed out waiting for operator: %w", err)
73+
}
74+
75+
return &ext, nil
76+
}
77+
78+
func (ou *OperatorUpdate) setDefaults(catalogSrc *olmv1.CatalogSource) {
79+
if ou.OverrideUnset {
80+
if ou.Version == "" {
81+
ou.Version = catalogSrc.Version
82+
}
83+
if len(ou.Channels) == 0 {
84+
ou.Channels = catalogSrc.Channels
85+
}
86+
if ou.UpgradeConstraintPolicy == "" {
87+
ou.UpgradeConstraintPolicy = string(catalogSrc.UpgradeConstraintPolicy)
88+
}
89+
90+
return
91+
}
92+
93+
if ou.UpgradeConstraintPolicy == "" {
94+
ou.UpgradeConstraintPolicy = string(olmv1.UpgradeConstraintPolicyCatalogProvided)
95+
}
96+
}
97+
98+
func (ou *OperatorUpdate) needsUpdate(catalogSrc *olmv1.CatalogSource, constraintPolicy olmv1.UpgradeConstraintPolicy) bool {
99+
if catalogSrc.Version == ou.Version &&
100+
slices.Equal(catalogSrc.Channels, ou.Channels) &&
101+
catalogSrc.UpgradeConstraintPolicy == constraintPolicy {
102+
return false
103+
}
104+
105+
return true
106+
}

0 commit comments

Comments
 (0)