Skip to content

Commit 2da8d70

Browse files
jmprusiPer Goncalves da Silva
and
Per Goncalves da Silva
committed
Implement support for upgrade Edges
Signed-off-by: Joaquim Moreno Prusi <[email protected]> Signed-off-by: Per Goncalves da Silva <[email protected]> Co-authored-by: Per Goncalves da Silva <[email protected]>
1 parent 61b563f commit 2da8d70

File tree

41 files changed

+56329
-351
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+56329
-351
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ kind-cluster-cleanup: $(KIND) ## Delete the kind cluster
116116
$(KIND) delete cluster --name ${KIND_CLUSTER_NAME}
117117

118118
kind-load-test-artifacts: $(KIND) ## Load the e2e testdata container images into a kind cluster
119+
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/registry-v1/prometheus-operator.v0.37.0 -t localhost/testdata/bundles/registry-v1/prometheus-operator:v0.37.0
119120
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/registry-v1/prometheus-operator.v0.47.0 -t localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0
121+
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/registry-v1/prometheus-operator.v0.65.1 -t localhost/testdata/bundles/registry-v1/prometheus-operator:v0.65.1
120122
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/plain-v0/plain.v0.1.0 -t localhost/testdata/bundles/plain-v0/plain:v0.1.0
121123
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/catalogs -f $(TESTDATA_DIR)/catalogs/test-catalog.Dockerfile -t localhost/testdata/catalogs/test-catalog:e2e
124+
$(KIND) load docker-image localhost/testdata/bundles/registry-v1/prometheus-operator:v0.37.0 --name $(KIND_CLUSTER_NAME)
122125
$(KIND) load docker-image localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0 --name $(KIND_CLUSTER_NAME)
126+
$(KIND) load docker-image localhost/testdata/bundles/registry-v1/prometheus-operator:v0.65.1 --name $(KIND_CLUSTER_NAME)
123127
$(KIND) load docker-image localhost/testdata/bundles/plain-v0/plain:v0.1.0 --name $(KIND_CLUSTER_NAME)
124128
$(KIND) load docker-image localhost/testdata/catalogs/test-catalog:e2e --name $(KIND_CLUSTER_NAME)
125129

config/samples/operators_v1alpha1_operator.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ metadata:
1010
name: operator-sample
1111
spec:
1212
packageName: argocd-operator
13+
version: 0.6.0

internal/controllers/variable_source.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ func NewVariableSource(cl client.Client) variablesources.NestedVariableSource {
2929
func(inputVariableSource input.VariableSource) (input.VariableSource, error) {
3030
return variablesources.NewOperatorVariableSource(cl, inputVariableSource), nil
3131
},
32+
func(inputVariableSource input.VariableSource) (input.VariableSource, error) {
33+
return variablesources.NewBundleDeploymentVariableSource(cl, inputVariableSource), nil
34+
},
3235
func(inputVariableSource input.VariableSource) (input.VariableSource, error) {
3336
return variablesources.NewBundlesAndDepsVariableSource(inputVariableSource), nil
3437
},

internal/resolution/entities/bundle_entity.go

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ const (
2525

2626
// ----
2727

28-
type ChannelProperties struct {
29-
property.Channel
30-
Replaces string `json:"replaces,omitempty"`
31-
Skips []string `json:"skips,omitempty"`
32-
SkipRange string `json:"skipRange,omitempty"`
33-
}
34-
3528
type propertyRequirement bool
3629

3730
const (
@@ -46,6 +39,10 @@ type PackageRequired struct {
4639

4740
type GVK property.GVK
4841

42+
type Replaces struct {
43+
Replaces string `json:"replaces"`
44+
}
45+
4946
func (g GVK) String() string {
5047
return fmt.Sprintf(`group:"%s" version:"%s" kind:"%s"`, g.Group, g.Version, g.Kind)
5148
}
@@ -64,15 +61,16 @@ type BundleEntity struct {
6461
*input.Entity
6562

6663
// these properties are lazy loaded as they are requested
67-
bundlePackage *property.Package
68-
providedGVKs []GVK
69-
requiredGVKs []GVKRequired
70-
requiredPackages []PackageRequired
71-
channelProperties *ChannelProperties
72-
semVersion *semver.Version
73-
bundlePath string
74-
mediaType string
75-
mu sync.RWMutex
64+
bundlePackage *property.Package
65+
providedGVKs []GVK
66+
requiredGVKs []GVKRequired
67+
requiredPackages []PackageRequired
68+
channel *property.Channel
69+
replaces *Replaces
70+
semVersion *semver.Version
71+
bundlePath string
72+
mediaType string
73+
mu sync.RWMutex
7674
}
7775

7876
func NewBundleEntity(entity *input.Entity) *BundleEntity {
@@ -121,14 +119,35 @@ func (b *BundleEntity) ChannelName() (string, error) {
121119
if err := b.loadChannelProperties(); err != nil {
122120
return "", err
123121
}
124-
return b.channelProperties.ChannelName, nil
122+
return b.channel.ChannelName, nil
125123
}
126124

127-
func (b *BundleEntity) ChannelProperties() (*ChannelProperties, error) {
125+
func (b *BundleEntity) Channel() (*property.Channel, error) {
128126
if err := b.loadChannelProperties(); err != nil {
129127
return nil, err
130128
}
131-
return b.channelProperties, nil
129+
return b.channel, nil
130+
}
131+
132+
func (b *BundleEntity) Replaces() (string, error) {
133+
if err := b.loadReplaces(); err != nil {
134+
return "", err
135+
}
136+
return b.replaces.Replaces, nil
137+
}
138+
139+
func (b *BundleEntity) loadReplaces() error {
140+
b.mu.Lock()
141+
defer b.mu.Unlock()
142+
if b.replaces == nil {
143+
// TODO: move property name to constant
144+
replaces, err := loadFromEntity[Replaces](b.Entity, "olm.replaces", optional)
145+
if err != nil {
146+
return fmt.Errorf("error determining replaces for entity '%s': %w", b.ID, err)
147+
}
148+
b.replaces = &replaces
149+
}
150+
return nil
132151
}
133152

134153
func (b *BundleEntity) BundlePath() (string, error) {
@@ -228,12 +247,12 @@ func (b *BundleEntity) loadRequiredPackages() error {
228247
func (b *BundleEntity) loadChannelProperties() error {
229248
b.mu.Lock()
230249
defer b.mu.Unlock()
231-
if b.channelProperties == nil {
232-
channel, err := loadFromEntity[ChannelProperties](b.Entity, property.TypeChannel, required)
250+
if b.channel == nil {
251+
channel, err := loadFromEntity[property.Channel](b.Entity, property.TypeChannel, required)
233252
if err != nil {
234253
return fmt.Errorf("error determining bundle channel properties for entity '%s': %w", b.ID, err)
235254
}
236-
b.channelProperties = &channel
255+
b.channel = &channel
237256
}
238257
return nil
239258
}
@@ -255,6 +274,8 @@ func loadFromEntity[T interface{}](entity *input.Entity, propertyName string, re
255274
deserializedProperty := *new(T)
256275
propertyValue, ok := entity.Properties[propertyName]
257276
if ok {
277+
// TODO: In order to avoid invalid properties we should use a decoder that only allows the properties we expect.
278+
// ie. decoder.DisallowUnknownFields()
258279
if err := json.Unmarshal([]byte(propertyValue), &deserializedProperty); err != nil {
259280
return deserializedProperty, fmt.Errorf("property '%s' ('%s') could not be parsed: %w", propertyName, propertyValue, err)
260281
}

internal/resolution/entities/bundle_entity_test.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,28 +205,24 @@ var _ = Describe("BundleEntity", func() {
205205
})
206206
})
207207

208-
Describe("ChannelProperties", func() {
208+
Describe("Channel", func() {
209209
It("should return the bundle channel properties if present", func() {
210210
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{
211211
"olm.channel": `{"channelName":"beta","priority":0, "replaces": "bundle.v1.0.0", "skips": ["bundle.v0.9.0", "bundle.v0.9.6"], "skipRange": ">=0.9.0 <=0.9.6"}`,
212212
})
213213
bundleEntity := olmentity.NewBundleEntity(entity)
214-
channelProperties, err := bundleEntity.ChannelProperties()
214+
channelProperties, err := bundleEntity.Channel()
215215
Expect(err).ToNot(HaveOccurred())
216-
Expect(*channelProperties).To(Equal(olmentity.ChannelProperties{
217-
Channel: property.Channel{
218-
ChannelName: "beta",
219-
Priority: 0,
220-
},
221-
Replaces: "bundle.v1.0.0",
222-
Skips: []string{"bundle.v0.9.0", "bundle.v0.9.6"},
223-
SkipRange: ">=0.9.0 <=0.9.6",
224-
}))
216+
Expect(*channelProperties).To(Equal(property.Channel{
217+
ChannelName: "beta",
218+
Priority: 0,
219+
},
220+
))
225221
})
226222
It("should return an error if the property is not found", func() {
227223
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{})
228224
bundleEntity := olmentity.NewBundleEntity(entity)
229-
channelProperties, err := bundleEntity.ChannelProperties()
225+
channelProperties, err := bundleEntity.Channel()
230226
Expect(channelProperties).To(BeNil())
231227
Expect(err.Error()).To(Equal("error determining bundle channel properties for entity 'operatorhub/prometheus/0.14.0': required property 'olm.channel' not found"))
232228
})
@@ -235,7 +231,7 @@ var _ = Describe("BundleEntity", func() {
235231
"olm.channel": "badChannelPropertiesStructure",
236232
})
237233
bundleEntity := olmentity.NewBundleEntity(entity)
238-
channelProperties, err := bundleEntity.ChannelProperties()
234+
channelProperties, err := bundleEntity.Channel()
239235
Expect(channelProperties).To(BeNil())
240236
Expect(err.Error()).To(Equal("error determining bundle channel properties for entity 'operatorhub/prometheus/0.14.0': property 'olm.channel' ('badChannelPropertiesStructure') could not be parsed: invalid character 'b' looking for beginning of value"))
241237
})
@@ -269,6 +265,27 @@ var _ = Describe("BundleEntity", func() {
269265
})
270266
})
271267

268+
Describe("Replaces", func() {
269+
It("should return the replaces property if present", func() {
270+
entity := input.NewEntity("test", map[string]string{
271+
"olm.replaces": `{"replaces": "test.v0.2.0"}`,
272+
})
273+
bundleEntity := olmentity.NewBundleEntity(entity)
274+
replaces, err := bundleEntity.Replaces()
275+
Expect(err).ToNot(HaveOccurred())
276+
Expect(replaces).To(Equal("test.v0.2.0"))
277+
})
278+
It("should not return an error if the property is not found", func() {
279+
entity := input.NewEntity("test", map[string]string{
280+
"olm.thingy": `{"whatever":"this"}`,
281+
})
282+
bundleEntity := olmentity.NewBundleEntity(entity)
283+
replaces, err := bundleEntity.Replaces()
284+
Expect(replaces).To(BeEmpty())
285+
Expect(err).NotTo(HaveOccurred())
286+
})
287+
})
288+
272289
Describe("MediaType", func() {
273290
It("should return the bundle mediatype property if present", func() {
274291
entity := input.NewEntity("operatorhub/prometheus/0.14.0", map[string]string{

internal/resolution/entitysources/catalogdsource.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ func (es *CatalogdEntitySource) Iterate(ctx context.Context, fn input.IteratorFu
7171
return nil
7272
}
7373

74+
type replacesProperty struct {
75+
Replaces string `json:"replaces"`
76+
}
77+
7478
func getEntities(ctx context.Context, client client.Client) (input.EntityList, error) {
7579
entityList := input.EntityList{}
7680
bundleMetadatas, packageMetdatas, err := fetchMetadata(ctx, client)
@@ -106,6 +110,9 @@ func getEntities(ctx context.Context, client client.Client) (input.EntityList, e
106110
if catalogScopedEntryName == bundle.Name {
107111
channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0})
108112
props[property.TypeChannel] = string(channelValue)
113+
// TODO(jmprusi): Add the proper PropertyType for this
114+
replacesValue, _ := json.Marshal(replacesProperty{Replaces: b.Replaces})
115+
props["olm.replaces"] = string(replacesValue)
109116
entity := input.Entity{
110117
ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Spec.Package, ch.Name)),
111118
Properties: props,

internal/resolution/util/predicates/predicates.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,25 @@ func ProvidesGVK(gvk *olmentity.GVK) input.Predicate {
5656
return false
5757
}
5858
}
59+
60+
func WithBundleImage(bundleImage string) input.Predicate {
61+
return func(entity *input.Entity) bool {
62+
bundleEntity := olmentity.NewBundleEntity(entity)
63+
bundlePath, err := bundleEntity.BundlePath()
64+
if err != nil {
65+
return false
66+
}
67+
return bundlePath == bundleImage
68+
}
69+
}
70+
71+
func Replaces(bundleID string) input.Predicate {
72+
return func(entity *input.Entity) bool {
73+
bundleEntity := olmentity.NewBundleEntity(entity)
74+
replaces, err := bundleEntity.Replaces()
75+
if err != nil {
76+
return false
77+
}
78+
return replaces == bundleID
79+
}
80+
}

internal/resolution/util/predicates/predicates_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,24 @@ var _ = Describe("Predicates", func() {
6868
})(entity)).To(BeFalse())
6969
})
7070
})
71+
72+
Describe("WithBundleImage", func() {
73+
It("should return true when the entity provides the same bundle image", func() {
74+
entity := input.NewEntity("test", map[string]string{
75+
olmentity.PropertyBundlePath: `"registry.io/repo/image@sha256:1234567890"`,
76+
})
77+
Expect(predicates.WithBundleImage("registry.io/repo/image@sha256:1234567890")(entity)).To(BeTrue())
78+
Expect(predicates.WithBundleImage("registry.io/repo/image@sha256:0987654321")(entity)).To(BeFalse())
79+
})
80+
})
81+
82+
Describe("Replaces", func() {
83+
It("should return true when the entity provides the replaces property", func() {
84+
entity := input.NewEntity("test", map[string]string{
85+
"olm.replaces": `{"replaces": "test.v0.2.0"}`,
86+
})
87+
Expect(predicates.Replaces("test.v0.2.0")(entity)).To(BeTrue())
88+
Expect(predicates.Replaces("test.v0.1.0")(entity)).To(BeFalse())
89+
})
90+
})
7191
})

internal/resolution/util/sort/sort.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func packageOrder(e1, e2 *entities.BundleEntity) int {
5555
}
5656

5757
func channelOrder(e1, e2 *entities.BundleEntity) int {
58-
channelProperties1, err1 := e1.ChannelProperties()
59-
channelProperties2, err2 := e2.ChannelProperties()
58+
channelProperties1, err1 := e1.Channel()
59+
channelProperties2, err2 := e2.Channel()
6060
errComp := compareErrors(err1, err2)
6161
if errComp != 0 {
6262
return errComp
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package variables
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/operator-framework/deppy/pkg/deppy"
7+
"github.com/operator-framework/deppy/pkg/deppy/constraint"
8+
"github.com/operator-framework/deppy/pkg/deppy/input"
9+
10+
olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities"
11+
)
12+
13+
type InstalledPackageVariable struct {
14+
*input.SimpleVariable
15+
bundleEntities []*olmentity.BundleEntity
16+
}
17+
18+
func (r *InstalledPackageVariable) BundleEntities() []*olmentity.BundleEntity {
19+
return r.bundleEntities
20+
}
21+
22+
func NewInstalledPackageVariable(bundleImage string, bundleEntities []*olmentity.BundleEntity) *InstalledPackageVariable {
23+
id := deppy.IdentifierFromString(fmt.Sprintf("installed package %s", bundleImage))
24+
entityIDs := make([]deppy.Identifier, 0, len(bundleEntities))
25+
for _, bundle := range bundleEntities {
26+
entityIDs = append(entityIDs, bundle.ID)
27+
}
28+
return &InstalledPackageVariable{
29+
SimpleVariable: input.NewSimpleVariable(id, constraint.Mandatory(), constraint.Dependency(entityIDs...)),
30+
bundleEntities: bundleEntities,
31+
}
32+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package variablesources
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/operator-framework/deppy/pkg/deppy"
8+
"github.com/operator-framework/deppy/pkg/deppy/input"
9+
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
)
12+
13+
var _ input.VariableSource = &BundleDeploymentVariableSource{}
14+
15+
type BundleDeploymentVariableSource struct {
16+
client client.Client
17+
inputVariableSource input.VariableSource
18+
}
19+
20+
func NewBundleDeploymentVariableSource(cl client.Client, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource {
21+
return &BundleDeploymentVariableSource{
22+
client: cl,
23+
inputVariableSource: inputVariableSource,
24+
}
25+
}
26+
27+
func (o *BundleDeploymentVariableSource) GetVariables(ctx context.Context, entitySource input.EntitySource) ([]deppy.Variable, error) {
28+
variableSources := SliceVariableSource{}
29+
if o.inputVariableSource != nil {
30+
variableSources = append(variableSources, o.inputVariableSource)
31+
}
32+
33+
bundleDeployments := rukpakv1alpha1.BundleDeploymentList{}
34+
if err := o.client.List(ctx, &bundleDeployments); err != nil {
35+
return nil, err
36+
}
37+
38+
processed := map[string]struct{}{}
39+
if len(bundleDeployments.Items) > 1 {
40+
fmt.Println("hello")
41+
}
42+
for _, bundleDeployment := range bundleDeployments.Items {
43+
sourceImage := bundleDeployment.Spec.Template.Spec.Source.Image
44+
if sourceImage != nil && sourceImage.Ref != "" {
45+
if _, ok := processed[sourceImage.Ref]; ok {
46+
continue
47+
}
48+
processed[sourceImage.Ref] = struct{}{}
49+
ips, err := NewInstalledPackageVariableSource(bundleDeployment.Spec.Template.Spec.Source.Image.Ref)
50+
if err != nil {
51+
return nil, err
52+
}
53+
variableSources = append(variableSources, ips)
54+
}
55+
}
56+
57+
return variableSources.GetVariables(ctx, entitySource)
58+
}

0 commit comments

Comments
 (0)