Skip to content

Commit be4c488

Browse files
everettravenPer Goncalves da Silva
authored andcommitted
✨ refactor ClusterExtensionReconciler.reconcile() (operator-framework#1068)
* update reconciler with logical interface abstractions to simply unit testing by moving bundle validation logic to the resolver, moving installation of content behind a new Applier interface, and using the new contentmanager.Watcher interface for watching managed objects Signed-off-by: everettraven <[email protected]> * update testing Signed-off-by: everettraven <[email protected]> * update testing Signed-off-by: everettraven <[email protected]> * remove bundle validation unit tests from controller package Signed-off-by: everettraven <[email protected]> * make linter happy Signed-off-by: everettraven <[email protected]> * remove postrenderer, replace with map[string]string for labels Signed-off-by: everettraven <[email protected]> * update debugging error string Signed-off-by: everettraven <[email protected]> * rebase fixes Signed-off-by: everettraven <[email protected]> --------- Signed-off-by: everettraven <[email protected]>
1 parent b9f7d24 commit be4c488

File tree

11 files changed

+791
-361
lines changed

11 files changed

+791
-361
lines changed

cmd/manager/main.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747

4848
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
4949
"github.com/operator-framework/operator-controller/internal/action"
50+
"github.com/operator-framework/operator-controller/internal/applier"
5051
"github.com/operator-framework/operator-controller/internal/authentication"
5152
"github.com/operator-framework/operator-controller/internal/catalogmetadata/cache"
5253
catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
@@ -191,6 +192,7 @@ func main() {
191192
}
192193
return tempConfig, nil
193194
}
195+
194196
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
195197
helmclient.StorageNamespaceMapper(installNamespaceMapper),
196198
helmclient.ClientNamespaceMapper(installNamespaceMapper),
@@ -232,16 +234,6 @@ func main() {
232234
os.Exit(1)
233235
}
234236

235-
aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
236-
if err != nil {
237-
setupLog.Error(err, "unable to create apiextensions client")
238-
os.Exit(1)
239-
}
240-
241-
preflights := []controllers.Preflight{
242-
crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()),
243-
}
244-
245237
cl := mgr.GetClient()
246238

247239
catalogsCachePath := filepath.Join(cachePath, "catalogs")
@@ -264,16 +256,33 @@ func main() {
264256
},
265257
catalogClient.GetPackage,
266258
),
259+
Validations: []resolve.ValidationFunc{
260+
resolve.NoDependencyValidation,
261+
},
262+
}
263+
264+
aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
265+
if err != nil {
266+
setupLog.Error(err, "unable to create apiextensions client")
267+
os.Exit(1)
268+
}
269+
270+
preflights := []applier.Preflight{
271+
crdupgradesafety.NewPreflight(aeClient.CustomResourceDefinitions()),
272+
}
273+
274+
applier := &applier.Helm{
275+
ActionClientGetter: acg,
276+
Preflights: preflights,
267277
}
268278

269279
if err = (&controllers.ClusterExtensionReconciler{
270280
Client: cl,
271281
Resolver: resolver,
272-
ActionClientGetter: acg,
273282
Unpacker: unpacker,
283+
Applier: applier,
274284
InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg},
275285
Finalizers: clusterExtensionFinalizers,
276-
Preflights: preflights,
277286
Watcher: contentmanager.New(restConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()),
278287
}).SetupWithManager(mgr); err != nil {
279288
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")

internal/applier/helm.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package applier
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io/fs"
8+
"strings"
9+
10+
"helm.sh/helm/v3/pkg/action"
11+
"helm.sh/helm/v3/pkg/chart"
12+
"helm.sh/helm/v3/pkg/chartutil"
13+
"helm.sh/helm/v3/pkg/release"
14+
"helm.sh/helm/v3/pkg/storage/driver"
15+
corev1 "k8s.io/api/core/v1"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
18+
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
19+
20+
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
21+
"github.com/operator-framework/operator-controller/internal/rukpak/convert"
22+
"github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety"
23+
"github.com/operator-framework/operator-controller/internal/rukpak/util"
24+
)
25+
26+
const (
27+
StateNeedsInstall string = "NeedsInstall"
28+
StateNeedsUpgrade string = "NeedsUpgrade"
29+
StateUnchanged string = "Unchanged"
30+
StateError string = "Error"
31+
maxHelmReleaseHistory = 10
32+
)
33+
34+
// Preflight is a check that should be run before making any changes to the cluster
35+
type Preflight interface {
36+
// Install runs checks that should be successful prior
37+
// to installing the Helm release. It is provided
38+
// a Helm release and returns an error if the
39+
// check is unsuccessful
40+
Install(context.Context, *release.Release) error
41+
42+
// Upgrade runs checks that should be successful prior
43+
// to upgrading the Helm release. It is provided
44+
// a Helm release and returns an error if the
45+
// check is unsuccessful
46+
Upgrade(context.Context, *release.Release) error
47+
}
48+
49+
type Helm struct {
50+
ActionClientGetter helmclient.ActionClientGetter
51+
Preflights []Preflight
52+
}
53+
54+
func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1alpha1.ClusterExtension, labels map[string]string) ([]client.Object, string, error) {
55+
chrt, err := convert.RegistryV1ToHelmChart(ctx, contentFS, ext.Spec.InstallNamespace, []string{corev1.NamespaceAll})
56+
if err != nil {
57+
return nil, "", err
58+
}
59+
values := chartutil.Values{}
60+
61+
ac, err := h.ActionClientGetter.ActionClientFor(ctx, ext)
62+
if err != nil {
63+
return nil, "", err
64+
}
65+
66+
rel, desiredRel, state, err := h.getReleaseState(ac, ext, chrt, values, labels)
67+
if err != nil {
68+
return nil, "", err
69+
}
70+
71+
for _, preflight := range h.Preflights {
72+
if ext.Spec.Preflight != nil && ext.Spec.Preflight.CRDUpgradeSafety != nil {
73+
if _, ok := preflight.(*crdupgradesafety.Preflight); ok && ext.Spec.Preflight.CRDUpgradeSafety.Disabled {
74+
// Skip this preflight check because it is of type *crdupgradesafety.Preflight and the CRD Upgrade Safety
75+
// preflight check has been disabled
76+
continue
77+
}
78+
}
79+
switch state {
80+
case StateNeedsInstall:
81+
err := preflight.Install(ctx, desiredRel)
82+
if err != nil {
83+
return nil, state, err
84+
}
85+
case StateNeedsUpgrade:
86+
err := preflight.Upgrade(ctx, desiredRel)
87+
if err != nil {
88+
return nil, state, err
89+
}
90+
}
91+
}
92+
93+
switch state {
94+
case StateNeedsInstall:
95+
rel, err = ac.Install(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(install *action.Install) error {
96+
install.CreateNamespace = false
97+
install.Labels = labels
98+
return nil
99+
})
100+
if err != nil {
101+
return nil, state, err
102+
}
103+
case StateNeedsUpgrade:
104+
rel, err = ac.Upgrade(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(upgrade *action.Upgrade) error {
105+
upgrade.MaxHistory = maxHelmReleaseHistory
106+
upgrade.Labels = labels
107+
return nil
108+
})
109+
if err != nil {
110+
return nil, state, err
111+
}
112+
case StateUnchanged:
113+
if err := ac.Reconcile(rel); err != nil {
114+
return nil, state, err
115+
}
116+
default:
117+
return nil, state, fmt.Errorf("unexpected release state %q", state)
118+
}
119+
120+
relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name))
121+
if err != nil {
122+
return nil, state, err
123+
}
124+
125+
return relObjects, state, nil
126+
}
127+
128+
func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1alpha1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, labels map[string]string) (*release.Release, *release.Release, string, error) {
129+
currentRelease, err := cl.Get(ext.GetName())
130+
if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
131+
return nil, nil, StateError, err
132+
}
133+
if errors.Is(err, driver.ErrReleaseNotFound) {
134+
return nil, nil, StateNeedsInstall, nil
135+
}
136+
137+
if errors.Is(err, driver.ErrReleaseNotFound) {
138+
desiredRelease, err := cl.Install(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(i *action.Install) error {
139+
i.DryRun = true
140+
i.DryRunOption = "server"
141+
i.Labels = labels
142+
return nil
143+
})
144+
if err != nil {
145+
return nil, nil, StateError, err
146+
}
147+
return nil, desiredRelease, StateNeedsInstall, nil
148+
}
149+
desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(upgrade *action.Upgrade) error {
150+
upgrade.MaxHistory = maxHelmReleaseHistory
151+
upgrade.DryRun = true
152+
upgrade.DryRunOption = "server"
153+
upgrade.Labels = labels
154+
return nil
155+
})
156+
if err != nil {
157+
return currentRelease, nil, StateError, err
158+
}
159+
relState := StateUnchanged
160+
if desiredRelease.Manifest != currentRelease.Manifest ||
161+
currentRelease.Info.Status == release.StatusFailed ||
162+
currentRelease.Info.Status == release.StatusSuperseded {
163+
relState = StateNeedsUpgrade
164+
}
165+
return currentRelease, desiredRelease, relState, nil
166+
}

internal/catalogmetadata/filter/successors.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ func SuccessorsOf(installedBundle *ocv1alpha1.BundleMetadata, channels ...declcf
2020

2121
installedBundleVersion, err := mmsemver.NewVersion(installedBundle.Version)
2222
if err != nil {
23-
return nil, err
23+
return nil, fmt.Errorf("parsing installed bundle %q version %q: %w", installedBundle.Name, installedBundle.Version, err)
2424
}
2525

2626
installedVersionConstraint, err := mmsemver.NewConstraint(installedBundleVersion.String())
2727
if err != nil {
28-
return nil, err
28+
return nil, fmt.Errorf("parsing installed version constraint %q: %w", installedBundleVersion.String(), err)
2929
}
3030

3131
successorsPredicate, err := successors(installedBundle, channels...)
3232
if err != nil {
33-
return nil, err
33+
return nil, fmt.Errorf("getting successorsPredicate: %w", err)
3434
}
3535

3636
// We need either successors or current version (no upgrade)

0 commit comments

Comments
 (0)