Skip to content

Commit 8066c73

Browse files
Add command for updating olmv1 catalog (#232)
Signed-off-by: Rashmi Gottipati <chowdary.grashmi@gmail.com>
1 parent ec84fc0 commit 8066c73

File tree

5 files changed

+522
-0
lines changed

5 files changed

+522
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package olmv1
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/operator-framework/kubectl-operator/internal/cmd/internal/log"
7+
v1action "github.com/operator-framework/kubectl-operator/internal/pkg/v1/action"
8+
"github.com/operator-framework/kubectl-operator/pkg/action"
9+
)
10+
11+
// NewCatalogUpdateCmd allows updating a selected clustercatalog
12+
func NewCatalogUpdateCmd(cfg *action.Configuration) *cobra.Command {
13+
i := v1action.NewCatalogUpdate(cfg)
14+
i.Logf = log.Printf
15+
16+
var priority int32
17+
var pollInterval int
18+
var labels map[string]string
19+
20+
cmd := &cobra.Command{
21+
Use: "catalog <catalog>",
22+
Short: "Update a catalog",
23+
Args: cobra.ExactArgs(1),
24+
Run: func(cmd *cobra.Command, args []string) {
25+
i.CatalogName = args[0]
26+
if cmd.Flags().Changed("priority") {
27+
i.Priority = &priority
28+
}
29+
if cmd.Flags().Changed("source-poll-interval-minutes") {
30+
i.PollIntervalMinutes = &pollInterval
31+
}
32+
if cmd.Flags().Changed("labels") {
33+
i.Labels = labels
34+
}
35+
_, err := i.Run(cmd.Context())
36+
if err != nil {
37+
log.Fatalf("failed to update catalog: %v", err)
38+
}
39+
log.Printf("catalog %q updated", i.CatalogName)
40+
},
41+
}
42+
cmd.Flags().Int32Var(&priority, "priority", 0, "priority determines the likelihood of a catalog being selected in conflict scenarios")
43+
cmd.Flags().IntVar(&pollInterval, "source-poll-interval-minutes", 5, "catalog source polling interval [in minutes]. Set to 0 or -1 to remove the polling interval.")
44+
cmd.Flags().StringToStringVar(&labels, "labels", map[string]string{}, "labels that will be added to the catalog")
45+
cmd.Flags().StringVar(&i.AvailabilityMode, "availability-mode", "", "available means that the catalog should be active and serving data")
46+
cmd.Flags().StringVar(&i.ImageRef, "image", "", "Image reference for the catalog source. Leave unset to retain the current image.")
47+
cmd.Flags().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")
48+
49+
return cmd
50+
}

internal/cmd/olmv1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command {
4848
}
4949
updateCmd.AddCommand(
5050
olmv1.NewExtensionUpdateCmd(cfg),
51+
olmv1.NewCatalogUpdateCmd(cfg),
5152
)
5253

5354
installCmd := &cobra.Command{

internal/pkg/v1/action/action_suite_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ func newClusterCatalog(name string) *olmv1.ClusterCatalog {
110110

111111
type extensionOpt func(*olmv1.ClusterExtension)
112112

113+
type catalogOpt func(*olmv1.ClusterCatalog)
114+
113115
func withVersion(version string) extensionOpt {
114116
return func(ext *olmv1.ClusterExtension) {
115117
ext.Spec.Source.Catalog.Version = version
@@ -141,6 +143,48 @@ func withLabels(labels map[string]string) extensionOpt {
141143
}
142144
}
143145

146+
func withCatalogSourceType(sourceType olmv1.SourceType) catalogOpt {
147+
return func(catalog *olmv1.ClusterCatalog) {
148+
catalog.Spec.Source.Type = sourceType
149+
}
150+
}
151+
152+
func withCatalogSourcePriority(priority *int32) catalogOpt {
153+
return func(catalog *olmv1.ClusterCatalog) {
154+
catalog.Spec.Priority = *priority
155+
}
156+
}
157+
158+
func withCatalogPollInterval(pollInterval *int) catalogOpt {
159+
return func(catalog *olmv1.ClusterCatalog) {
160+
if catalog.Spec.Source.Image == nil {
161+
catalog.Spec.Source.Image = &olmv1.ImageSource{}
162+
}
163+
catalog.Spec.Source.Image.PollIntervalMinutes = pollInterval
164+
}
165+
}
166+
167+
func withCatalogImageRef(ref string) catalogOpt {
168+
return func(catalog *olmv1.ClusterCatalog) {
169+
if catalog.Spec.Source.Image == nil {
170+
catalog.Spec.Source.Image = &olmv1.ImageSource{}
171+
}
172+
catalog.Spec.Source.Image.Ref = ref
173+
}
174+
}
175+
176+
func withCatalogAvailabilityMode(mode olmv1.AvailabilityMode) catalogOpt {
177+
return func(catalog *olmv1.ClusterCatalog) {
178+
catalog.Spec.AvailabilityMode = mode
179+
}
180+
}
181+
182+
func withCatalogLabels(labels map[string]string) catalogOpt {
183+
return func(catalog *olmv1.ClusterCatalog) {
184+
catalog.Labels = labels
185+
}
186+
}
187+
144188
func buildExtension(packageName string, opts ...extensionOpt) *olmv1.ClusterExtension {
145189
ext := &olmv1.ClusterExtension{
146190
Spec: olmv1.ClusterExtensionSpec{
@@ -173,3 +217,22 @@ func updateExtensionConditionStatus(name string, cl client.Client, typ string, s
173217

174218
return cl.Update(context.TODO(), &ext)
175219
}
220+
221+
func buildCatalog(catalogName string, opts ...catalogOpt) *olmv1.ClusterCatalog {
222+
catalog := &olmv1.ClusterCatalog{
223+
ObjectMeta: metav1.ObjectMeta{
224+
Name: catalogName,
225+
},
226+
Spec: olmv1.ClusterCatalogSpec{
227+
Source: olmv1.CatalogSource{
228+
Type: olmv1.SourceTypeImage,
229+
},
230+
},
231+
}
232+
catalog.SetName(catalogName)
233+
for _, opt := range opts {
234+
opt(catalog)
235+
}
236+
237+
return catalog
238+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package action
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
8+
"k8s.io/apimachinery/pkg/types"
9+
10+
olmv1 "github.com/operator-framework/operator-controller/api/v1"
11+
12+
"github.com/operator-framework/kubectl-operator/pkg/action"
13+
)
14+
15+
type CatalogUpdate struct {
16+
config *action.Configuration
17+
CatalogName string
18+
19+
Priority *int32
20+
PollIntervalMinutes *int
21+
Labels map[string]string
22+
AvailabilityMode string
23+
ImageRef string
24+
IgnoreUnset bool
25+
26+
Logf func(string, ...interface{})
27+
}
28+
29+
func NewCatalogUpdate(config *action.Configuration) *CatalogUpdate {
30+
return &CatalogUpdate{
31+
config: config,
32+
Logf: func(string, ...interface{}) {},
33+
}
34+
}
35+
36+
func (cu *CatalogUpdate) Run(ctx context.Context) (*olmv1.ClusterCatalog, error) {
37+
var catalog olmv1.ClusterCatalog
38+
var err error
39+
40+
cuKey := types.NamespacedName{
41+
Name: cu.CatalogName,
42+
Namespace: cu.config.Namespace,
43+
}
44+
if err = cu.config.Client.Get(ctx, cuKey, &catalog); err != nil {
45+
return nil, err
46+
}
47+
48+
if catalog.Spec.Source.Type != olmv1.SourceTypeImage {
49+
return nil, fmt.Errorf("unrecognized source type: %q", catalog.Spec.Source.Type)
50+
}
51+
52+
if cu.ImageRef != "" && !isValidImageRef(cu.ImageRef) {
53+
return nil, fmt.Errorf("invalid image reference: %q, it must be a valid image reference format", cu.ImageRef)
54+
}
55+
56+
cu.setDefaults(&catalog)
57+
58+
cu.setUpdatedCatalog(&catalog)
59+
if err := cu.config.Client.Update(ctx, &catalog); err != nil {
60+
return nil, err
61+
}
62+
63+
cu.Logf("Updating catalog %q in namespace %q", cu.CatalogName, cu.config.Namespace)
64+
65+
return &catalog, nil
66+
}
67+
68+
func (cu *CatalogUpdate) setUpdatedCatalog(catalog *olmv1.ClusterCatalog) {
69+
existingLabels := catalog.GetLabels()
70+
if existingLabels == nil {
71+
existingLabels = make(map[string]string)
72+
}
73+
if cu.Labels != nil {
74+
for k, v := range cu.Labels {
75+
if v == "" {
76+
delete(existingLabels, k)
77+
} else {
78+
existingLabels[k] = v
79+
}
80+
}
81+
catalog.SetLabels(existingLabels)
82+
}
83+
84+
if cu.Priority != nil {
85+
catalog.Spec.Priority = *cu.Priority
86+
}
87+
88+
if catalog.Spec.Source.Image == nil {
89+
catalog.Spec.Source.Image = &olmv1.ImageSource{}
90+
}
91+
92+
if cu.PollIntervalMinutes != nil {
93+
if *cu.PollIntervalMinutes == 0 || *cu.PollIntervalMinutes == -1 {
94+
catalog.Spec.Source.Image.PollIntervalMinutes = nil
95+
} else {
96+
catalog.Spec.Source.Image.PollIntervalMinutes = cu.PollIntervalMinutes
97+
}
98+
}
99+
100+
if cu.ImageRef != "" {
101+
catalog.Spec.Source.Image.Ref = cu.ImageRef
102+
}
103+
104+
if cu.AvailabilityMode != "" {
105+
catalog.Spec.AvailabilityMode = olmv1.AvailabilityMode(cu.AvailabilityMode)
106+
}
107+
}
108+
109+
func (cu *CatalogUpdate) setDefaults(catalog *olmv1.ClusterCatalog) {
110+
if !cu.IgnoreUnset {
111+
return
112+
}
113+
114+
catalogSrc := catalog.Spec.Source
115+
116+
if cu.Priority == nil {
117+
cu.Priority = &catalog.Spec.Priority
118+
}
119+
120+
if cu.PollIntervalMinutes == nil && catalogSrc.Image != nil && catalogSrc.Image.PollIntervalMinutes != nil {
121+
cu.PollIntervalMinutes = catalogSrc.Image.PollIntervalMinutes
122+
}
123+
124+
if cu.ImageRef == "" && catalogSrc.Image != nil {
125+
cu.ImageRef = catalogSrc.Image.Ref
126+
}
127+
if cu.AvailabilityMode == "" {
128+
cu.AvailabilityMode = string(catalog.Spec.AvailabilityMode)
129+
}
130+
if len(cu.Labels) == 0 {
131+
cu.Labels = catalog.Labels
132+
}
133+
}
134+
135+
func isValidImageRef(imageRef string) bool {
136+
var imageRefRegex = regexp.MustCompile(`^([a-z0-9]+(\.[a-z0-9]+)*(:[0-9]+)?/)?[a-z0-9-_]+(/[a-z0-9-_]+)*(:[a-zA-Z0-9_\.-]+)?(@sha256:[a-fA-F0-9]{64})?$`)
137+
138+
return imageRefRegex.MatchString(imageRef)
139+
}

0 commit comments

Comments
 (0)