-
Notifications
You must be signed in to change notification settings - Fork 43
✨ Add command to update olmv1 operator #225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
LalatenduMohanty marked this conversation as resolved.
Show resolved
Hide resolved
azych marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
}, | ||
camilamacedo86 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
bindOperatorUpdateFlags(cmd.Flags(), i) | ||
azych marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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") | ||
} | ||
azych marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which lint error is this masking? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. internal/pkg/v1/action/action_suite_test.go:83:27 unparam I don't think this needs to be 'fixed', hence |
||
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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
azych marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
} |
Uh oh!
There was an error while loading. Please reload this page.