Skip to content

Commit 04efb43

Browse files
committed
WIP
1 parent 22afecf commit 04efb43

File tree

8 files changed

+295
-13
lines changed

8 files changed

+295
-13
lines changed

internal/controllers/operator_controller.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha
159159

160160
// Ensure a BundleDeployment exists with its bundle source from the bundle
161161
// image we just looked up in the solution.
162-
dep := r.generateExpectedBundleDeployment(*op, bundleImage)
162+
dep := r.generateExpectedBundleDeployment(*op, bundleEntity)
163163
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
164164
// originally Reason: operatorsv1alpha1.ReasonInstallationFailed
165165
op.Status.InstalledBundleResource = ""
@@ -245,17 +245,45 @@ func (r *OperatorReconciler) getBundleEntityFromSolution(solution *solver.Soluti
245245
return nil, fmt.Errorf("entity for package %q not found in solution", packageName)
246246
}
247247

248-
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string) *unstructured.Unstructured {
248+
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundleEntity *entity.BundleEntity) *unstructured.Unstructured {
249249
// We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver.
250250
// If you use a typed object, any default values from that struct get serialized into the JSON patch, which could
251251
// cause unrelated fields to be patched back to the default value even though that isn't the intention. Using an
252252
// unstructured ensures that the patch contains only what is specified. Using unstructured like this is basically
253253
// identical to "kubectl apply -f"
254+
255+
// TODO(jmprusi): return err in func?
256+
bundlePath, err := bundleEntity.BundlePath()
257+
if err != nil {
258+
return nil
259+
}
260+
261+
channelName, err := bundleEntity.ChannelName()
262+
if err != nil {
263+
return nil
264+
}
265+
266+
packageName, err := bundleEntity.PackageName()
267+
if err != nil {
268+
return nil
269+
}
270+
271+
packageVersion, err := bundleEntity.Version()
272+
if err != nil {
273+
return nil
274+
}
275+
276+
// TODO(jmprusi): create a proper const for operators.operatorframework.io/operator-ref
254277
bd := &unstructured.Unstructured{Object: map[string]interface{}{
255278
"apiVersion": rukpakv1alpha1.GroupVersion.String(),
256279
"kind": rukpakv1alpha1.BundleDeploymentKind,
257280
"metadata": map[string]interface{}{
258281
"name": o.GetName(),
282+
"annotations": map[string]interface{}{
283+
"operators.operatorframework.io/package": packageName,
284+
"operators.operatorframework.io/channel": channelName,
285+
"operators.operatorframework.io/version": packageVersion,
286+
},
259287
},
260288
"spec": map[string]interface{}{
261289
// TODO: Don't assume plain provisioner

internal/resolution/entitysources/catalogdsource.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ func getEntities(ctx context.Context, client client.Client) (input.EntityList, e
102102
if catalogScopedEntryName == bundle.Name {
103103
channelValue, _ := json.Marshal(property.Channel{ChannelName: ch.Name, Priority: 0})
104104
props[property.TypeChannel] = string(channelValue)
105+
// TODO(jmprusi): Add the proper PropertyType for this
106+
replacesValue, _ := json.Marshal(replacesProperty{Replaces: b.Replaces})
107+
props["olm.replaces"] = string(replacesValue)
105108
entity := input.Entity{
106109
ID: deppy.IdentifierFromString(fmt.Sprintf("%s%s%s", bundle.Name, bundle.Spec.Package, ch.Name)),
107110
Properties: props,
@@ -114,6 +117,10 @@ func getEntities(ctx context.Context, client client.Client) (input.EntityList, e
114117
return entities, nil
115118
}
116119

120+
type replacesProperty struct {
121+
Replaces string `json:"replaces"`
122+
}
123+
117124
func fetchMetadata(ctx context.Context, client client.Client) (catalogd.BundleMetadataList, map[string]catalogd.Package, error) {
118125
packageMetdatas := catalogd.PackageList{}
119126
if err := client.List(ctx, &packageMetdatas); err != nil {

internal/resolution/resolver.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55

66
"github.com/operator-framework/deppy/pkg/deppy/input"
77
"github.com/operator-framework/deppy/pkg/deppy/solver"
8-
"sigs.k8s.io/controller-runtime/pkg/client"
9-
108
"github.com/operator-framework/operator-controller/api/v1alpha1"
119
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm"
10+
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
1212
)
1313

1414
type OperatorResolver struct {
@@ -32,7 +32,12 @@ func (o *OperatorResolver) Resolve(ctx context.Context) (*solver.Solution, error
3232
return &solver.Solution{}, nil
3333
}
3434

35-
olmVariableSource := olm.NewOLMVariableSource(operatorList.Items...)
35+
bundleDeploymentList := rukpakv1alpha1.BundleDeploymentList{}
36+
if err := o.client.List(ctx, &bundleDeploymentList); err != nil {
37+
return nil, err
38+
}
39+
40+
olmVariableSource := olm.NewOLMVariableSource(operatorList.Items, bundleDeploymentList.Items)
3641
deppySolver := solver.NewDeppySolver(o.entitySource, olmVariableSource)
3742

3843
solution, err := deppySolver.Solve(ctx)

internal/resolution/variable_sources/bundles_and_dependencies/bundles_and_dependencies.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/operator-framework/deppy/pkg/deppy/input"
1212

1313
olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
14+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/installed_package"
1415
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/required_package"
1516
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/util/predicates"
1617
entitysort "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/util/sort"
@@ -76,6 +77,8 @@ func (b *BundlesAndDepsVariableSource) GetVariables(ctx context.Context, entityS
7677
switch v := variable.(type) {
7778
case *required_package.RequiredPackageVariable:
7879
bundleEntityQueue = append(bundleEntityQueue, v.BundleEntities()...)
80+
case *installed_package.InstalledPackageVariable:
81+
bundleEntityQueue = append(bundleEntityQueue, v.BundleEntities()...)
7982
}
8083
}
8184

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package installed_package
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/blang/semver/v4"
9+
"github.com/operator-framework/deppy/pkg/deppy"
10+
"github.com/operator-framework/deppy/pkg/deppy/constraint"
11+
"github.com/operator-framework/deppy/pkg/deppy/input"
12+
13+
olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
14+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/util/predicates"
15+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/util/sort"
16+
)
17+
18+
type InstalledPackageVariable struct {
19+
*input.SimpleVariable
20+
bundleEntities []*olmentity.BundleEntity
21+
}
22+
23+
func (r *InstalledPackageVariable) BundleEntities() []*olmentity.BundleEntity {
24+
return r.bundleEntities
25+
}
26+
27+
func NewInstalledPackageVariable(packageName string, bundleEntities []*olmentity.BundleEntity) *InstalledPackageVariable {
28+
id := deppy.IdentifierFromString(fmt.Sprintf("installed package %s", packageName))
29+
var entityIDs []deppy.Identifier
30+
for _, bundle := range bundleEntities {
31+
entityIDs = append(entityIDs, bundle.ID)
32+
}
33+
return &InstalledPackageVariable{
34+
SimpleVariable: input.NewSimpleVariable(id, constraint.Mandatory(), constraint.Dependency(entityIDs...)),
35+
bundleEntities: bundleEntities,
36+
}
37+
}
38+
39+
var _ input.VariableSource = &InstalledPackageVariableSource{}
40+
41+
type InstalledPackageVariableSource struct {
42+
packageName string
43+
version semver.Version
44+
channelName string
45+
}
46+
47+
func NewInstalledPackageVariableSource(packageName, version, channel string) (*InstalledPackageVariableSource, error) {
48+
if packageName == "" {
49+
return nil, fmt.Errorf("package name must not be empty")
50+
}
51+
52+
semverVersion, err := semver.Parse(version)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
//TODO(jmprusi): check version and channel
58+
return &InstalledPackageVariableSource{
59+
packageName: packageName,
60+
version: semverVersion,
61+
channelName: channel,
62+
}, nil
63+
}
64+
65+
// TODO(jmprusi): move this somewhere else?
66+
type replacesProperty struct {
67+
Replaces string `json:"replaces"`
68+
}
69+
70+
// TODO(jmprusi): move this somewhere else?
71+
type packageProperty struct {
72+
Package string `json:"packageName"`
73+
Version string `json:"version"`
74+
}
75+
76+
func (r *InstalledPackageVariableSource) GetVariables(ctx context.Context, entitySource input.EntitySource) ([]deppy.Variable, error) {
77+
validRange, err := semver.ParseRange(">=" + r.version.String())
78+
if err != nil {
79+
return nil, err
80+
}
81+
resultSet, err := entitySource.Filter(ctx, input.And(predicates.WithPackageName(r.packageName), predicates.InChannel(r.channelName), predicates.InSemverRange(validRange)))
82+
if err != nil {
83+
return nil, err
84+
}
85+
if len(resultSet) == 0 {
86+
return nil, r.notFoundError()
87+
}
88+
resultSet = resultSet.Sort(sort.ByChannelAndVersion)
89+
var bundleEntities []*olmentity.BundleEntity
90+
for i := 0; i < len(resultSet); i++ {
91+
92+
replacesJSON := resultSet[i].Properties["olm.replaces"]
93+
packageJSON := resultSet[i].Properties["olm.package"]
94+
95+
if replacesJSON == "" || packageJSON == "" {
96+
continue
97+
}
98+
99+
// unmarshal replaces and packages
100+
var replaces replacesProperty
101+
var packages packageProperty
102+
if err := json.Unmarshal([]byte(replacesJSON), &replaces); err != nil {
103+
return nil, err
104+
}
105+
if err := json.Unmarshal([]byte(packageJSON), &packages); err != nil {
106+
return nil, err
107+
}
108+
109+
version, err := semver.Parse(packages.Version)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
expectedReplace := fmt.Sprintf("%s.v%s", r.packageName, r.version.String())
115+
if r.version.Equals(version) || replaces.Replaces == expectedReplace {
116+
bundleEntities = append(bundleEntities, olmentity.NewBundleEntity(&resultSet[i]))
117+
}
118+
119+
}
120+
return []deppy.Variable{
121+
NewInstalledPackageVariable(r.packageName, bundleEntities),
122+
}, nil
123+
}
124+
125+
func (r *InstalledPackageVariableSource) notFoundError() error {
126+
return fmt.Errorf("package '%s' not installed", r.packageName)
127+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package installed_package_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
"github.com/operator-framework/deppy/pkg/deppy"
10+
"github.com/operator-framework/deppy/pkg/deppy/input"
11+
"github.com/operator-framework/operator-registry/alpha/property"
12+
13+
olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
14+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/installed_package"
15+
)
16+
17+
func TestInstalledPackage(t *testing.T) {
18+
RegisterFailHandler(Fail)
19+
RunSpecs(t, "InstalledPackageVariableSource Suite")
20+
}
21+
22+
var _ = Describe("installedPackageVariable", func() {
23+
var (
24+
rpv *installed_package.InstalledPackageVariable
25+
packageName string
26+
bundleEntities []*olmentity.BundleEntity
27+
)
28+
29+
BeforeEach(func() {
30+
packageName = "test-package"
31+
bundleEntities = []*olmentity.BundleEntity{
32+
olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{
33+
property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`,
34+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
35+
"olm.replaces": `{"packageName": "test-package", "version": "0.0.0"}`,
36+
})),
37+
olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{
38+
property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`,
39+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
40+
"olm.replaces": `{"packageName": "test-package", "version": "1.0.0"}`,
41+
})),
42+
olmentity.NewBundleEntity(input.NewEntity("bundle-3", map[string]string{
43+
property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`,
44+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
45+
"olm.replaces": `{"packageName": "test-package", "version": "2.0.0"}`,
46+
})),
47+
}
48+
rpv = installed_package.NewInstalledPackageVariable(packageName, bundleEntities)
49+
})
50+
51+
It("should return the correct package name", func() {
52+
Expect(rpv.Identifier()).To(Equal(deppy.IdentifierFromString(fmt.Sprintf("installed package %s", packageName))))
53+
})
54+
55+
It("should return the correct bundle entities", func() {
56+
Expect(rpv.BundleEntities()).To(Equal(bundleEntities))
57+
})
58+
})

internal/resolution/variable_sources/olm/olm.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@ import (
99
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
1010
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies"
1111
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/crd_constraints"
12+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/installed_package"
1213
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/required_package"
14+
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
1315
)
1416

1517
var _ input.VariableSource = &OLMVariableSource{}
1618

1719
type OLMVariableSource struct {
18-
operators []operatorsv1alpha1.Operator
20+
operators []operatorsv1alpha1.Operator
21+
bundleDeployments []rukpakv1alpha1.BundleDeployment
1922
}
2023

21-
func NewOLMVariableSource(operators ...operatorsv1alpha1.Operator) *OLMVariableSource {
24+
func NewOLMVariableSource(operators []operatorsv1alpha1.Operator, bundleDeployments []rukpakv1alpha1.BundleDeployment) *OLMVariableSource {
2225
return &OLMVariableSource{
23-
operators: operators,
26+
operators: operators,
27+
bundleDeployments: bundleDeployments,
2428
}
2529
}
2630

@@ -36,11 +40,38 @@ func (o *OLMVariableSource) GetVariables(ctx context.Context, entitySource input
3640
inputVariableSources = append(inputVariableSources, rps)
3741
}
3842

43+
// Scan for all the bundleDeployments and look for the one with the proper annotation
44+
// with that we will have the actual version and packagename of the installed package.
45+
//
46+
// That information can be used to check if the new desired version is compatible with the installed one.
47+
//
48+
for _, bundleDeployment := range o.bundleDeployments {
49+
if _, ok := bundleDeployment.Annotations["operators.operatorframework.io/package"]; !ok {
50+
continue
51+
}
52+
ips, err := o.installedPackage(&bundleDeployment)
53+
if err != nil {
54+
return nil, err
55+
}
56+
inputVariableSources = append(inputVariableSources, ips)
57+
}
58+
3959
// build variable source pipeline
4060
variableSource := crd_constraints.NewCRDUniquenessConstraintsVariableSource(bundles_and_dependencies.NewBundlesAndDepsVariableSource(inputVariableSources...))
61+
4162
return variableSource.GetVariables(ctx, entitySource)
4263
}
4364

65+
func (o *OLMVariableSource) installedPackage(bundleDeployment *rukpakv1alpha1.BundleDeployment) (*installed_package.InstalledPackageVariableSource, error) {
66+
67+
// TODO(jmprusi): proper if ... validation
68+
version := bundleDeployment.Annotations["operators.operatorframework.io/version"]
69+
channel := bundleDeployment.Annotations["operators.operatorframework.io/channel"]
70+
pkg := bundleDeployment.Annotations["operators.operatorframework.io/package"]
71+
72+
return installed_package.NewInstalledPackageVariableSource(pkg, version, channel)
73+
}
74+
4475
func (o *OLMVariableSource) requiredPackageFromOperator(operator *operatorsv1alpha1.Operator) (*required_package.RequiredPackageVariableSource, error) {
4576
var opts []required_package.RequiredPackageOption
4677
if operator.Spec.Version != "" {

0 commit comments

Comments
 (0)