-
Notifications
You must be signed in to change notification settings - Fork 48
/
installed_package.go
123 lines (103 loc) · 4.71 KB
/
installed_package.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package variablesources
import (
"context"
"fmt"
"sort"
mmsemver "github.com/Masterminds/semver/v3"
"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/input"
"github.com/operator-framework/operator-controller/internal/catalogmetadata"
catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter"
catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort"
"github.com/operator-framework/operator-controller/internal/resolution/variables"
"github.com/operator-framework/operator-controller/pkg/features"
)
var _ input.VariableSource = &InstalledPackageVariableSource{}
type InstalledPackageVariableSource struct {
catalogClient BundleProvider
successors successorsFunc
bundleImage string
}
func (r *InstalledPackageVariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, error) {
allBundles, err := r.catalogClient.Bundles(ctx)
if err != nil {
return nil, err
}
// find corresponding bundle for the installed content
resultSet := catalogfilter.Filter(allBundles, catalogfilter.WithBundleImage(r.bundleImage))
if len(resultSet) == 0 {
return nil, r.notFoundError()
}
// TODO: fast follow - we should check whether we are already supporting the channel attribute in the operator spec.
// if so, we should take the value from spec of the operator CR in the owner ref of the bundle deployment.
// If that channel is set, we need to update the filter above to filter by channel as well.
sort.SliceStable(resultSet, func(i, j int) bool {
return catalogsort.ByVersion(resultSet[i], resultSet[j])
})
installedBundle := resultSet[0]
upgradeEdges, err := r.successors(allBundles, installedBundle)
if err != nil {
return nil, err
}
// you can always upgrade to yourself, i.e. not upgrade
upgradeEdges = append(upgradeEdges, installedBundle)
return []deppy.Variable{
variables.NewInstalledPackageVariable(installedBundle.Package, upgradeEdges),
}, nil
}
func (r *InstalledPackageVariableSource) notFoundError() error {
return fmt.Errorf("bundleImage %q not found", r.bundleImage)
}
func NewInstalledPackageVariableSource(catalogClient BundleProvider, bundleImage string) (*InstalledPackageVariableSource, error) {
successors := legacySemanticsSuccessors
if features.OperatorControllerFeatureGate.Enabled(features.ForceSemverUpgradeConstraints) {
successors = semverSuccessors
}
return &InstalledPackageVariableSource{
catalogClient: catalogClient,
bundleImage: bundleImage,
successors: successors,
}, nil
}
// successorsFunc must return successors of a currently installed bundle
// from a list of all bundles provided to the function.
// Must not return installed bundle as a successor
type successorsFunc func(allBundles []*catalogmetadata.Bundle, installedBundle *catalogmetadata.Bundle) ([]*catalogmetadata.Bundle, error)
// legacySemanticsSuccessors returns successors based on legacy OLMv0 semantics
// which rely on Replaces, Skips and skipRange.
func legacySemanticsSuccessors(allBundles []*catalogmetadata.Bundle, installedBundle *catalogmetadata.Bundle) ([]*catalogmetadata.Bundle, error) {
// find the bundles that replace the bundle provided
// TODO: this algorithm does not yet consider skips and skipRange
upgradeEdges := catalogfilter.Filter(allBundles, catalogfilter.And(
catalogfilter.WithPackageName(installedBundle.Package),
catalogfilter.Replaces(installedBundle.Name),
))
sort.SliceStable(upgradeEdges, func(i, j int) bool {
return catalogsort.ByVersion(upgradeEdges[i], upgradeEdges[j])
})
return upgradeEdges, nil
}
// semverSuccessors returns successors based on Semver.
// Successors will not include versions outside the major version of the
// installed bundle as major version is intended to indicate breaking changes.
func semverSuccessors(allBundles []*catalogmetadata.Bundle, installedBundle *catalogmetadata.Bundle) ([]*catalogmetadata.Bundle, error) {
currentVersion, err := installedBundle.Version()
if err != nil {
return nil, err
}
// Based on current version create a caret range comparison constraint
// to allow only minor and patch version as successors and exclude current version.
constraintStr := fmt.Sprintf("^%s, != %s", currentVersion.String(), currentVersion.String())
wantedVersionRangeConstraint, err := mmsemver.NewConstraint(constraintStr)
if err != nil {
return nil, err
}
upgradeEdges := catalogfilter.Filter(allBundles, catalogfilter.And(
catalogfilter.WithPackageName(installedBundle.Package),
catalogfilter.InMastermindsSemverRange(wantedVersionRangeConstraint),
))
sort.SliceStable(upgradeEdges, func(i, j int) bool {
return catalogsort.ByVersion(upgradeEdges[i], upgradeEdges[j])
})
return upgradeEdges, nil
}