Skip to content

Commit 1cb254c

Browse files
authored
Use Helm List operator to determine Deployed status (#1379)
Signed-off-by: Todd Short <tshort@redhat.com>
1 parent 6f42274 commit 1cb254c

File tree

10 files changed

+256
-47
lines changed

10 files changed

+256
-47
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ require (
1818
github.com/opencontainers/go-digest v1.0.0
1919
github.com/operator-framework/api v0.27.0
2020
github.com/operator-framework/catalogd v0.35.0
21-
github.com/operator-framework/helm-operator-plugins v0.5.0
21+
github.com/operator-framework/helm-operator-plugins v0.7.0
2222
github.com/operator-framework/operator-registry v1.47.0
2323
github.com/spf13/pflag v1.0.5
2424
github.com/stretchr/testify v1.9.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,8 @@ github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/
537537
github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM=
538538
github.com/operator-framework/catalogd v0.35.0 h1:2lQPyHIzEfcciUjQ/fo4i/GE/sX2LBzd8S+nYxlvEHU=
539539
github.com/operator-framework/catalogd v0.35.0/go.mod h1:anZurjcFMBvbkuyqlJ98v9z+yjniPKqmhlyitk9DuBQ=
540-
github.com/operator-framework/helm-operator-plugins v0.5.0 h1:qph2OoECcI9mpuUBtOsWOMgvpx52mPTTSvzVxICsT04=
541-
github.com/operator-framework/helm-operator-plugins v0.5.0/go.mod h1:yVncrZ/FJNqedMil+055fk6sw8aMKRrget/AqGM0ig0=
540+
github.com/operator-framework/helm-operator-plugins v0.7.0 h1:YmtIWFc9BaNaDc5mk/dkG0P2BqPZOqpDvjWih5Fczuk=
541+
github.com/operator-framework/helm-operator-plugins v0.7.0/go.mod h1:fUUCJR3bWtMBZ1qdDhbwjacsBHi9uT576tF4u/DwOgQ=
542542
github.com/operator-framework/operator-lib v0.15.0 h1:0QeRM4PMtThqINpcFGCEBnIV3Z8u7/8fYLEx6mUtdcM=
543543
github.com/operator-framework/operator-lib v0.15.0/go.mod h1:ZxLvFuQ7bRWiTNBOqodbuNvcsy/Iq0kOygdxhlbNdI0=
544544
github.com/operator-framework/operator-registry v1.47.0 h1:Imr7X/W6FmXczwpIOXfnX8d6Snr1dzwWxkMG+lLAfhg=

internal/action/helm.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ func (a ActionClient) Get(name string, opts ...actionclient.GetOption) (*release
7575
return resp, err
7676
}
7777

78+
func (a ActionClient) History(name string, opts ...actionclient.HistoryOption) ([]*release.Release, error) {
79+
resp, err := a.ActionInterface.History(name, opts...)
80+
err = a.actionClientErrorTranslator(err)
81+
return resp, err
82+
}
83+
7884
func (a ActionClient) Reconcile(rel *release.Release) error {
7985
return a.actionClientErrorTranslator(a.ActionInterface.Reconcile(rel))
8086
}

internal/action/helm_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ func (m *mockActionClient) Get(name string, opts ...actionclient.GetOption) (*re
3030
return args.Get(0).(*release.Release), args.Error(1)
3131
}
3232

33+
func (m *mockActionClient) History(name string, opts ...actionclient.HistoryOption) ([]*release.Release, error) {
34+
args := m.Called(name, opts)
35+
if args.Get(0) == nil {
36+
return nil, args.Error(1)
37+
}
38+
rel := []*release.Release{
39+
args.Get(0).(*release.Release),
40+
}
41+
return rel, args.Error(1)
42+
}
43+
3344
func (m *mockActionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...actionclient.InstallOption) (*release.Release, error) {
3445
args := m.Called(name, namespace, chrt, vals, opts)
3546
if args.Get(0) == nil {
@@ -82,6 +93,7 @@ func TestActionClientErrorTranslation(t *testing.T) {
8293

8394
ac := new(mockActionClient)
8495
ac.On("Get", mock.Anything, mock.Anything).Return(nil, originalError)
96+
ac.On("History", mock.Anything, mock.Anything).Return(nil, originalError)
8597
ac.On("Install", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, originalError)
8698
ac.On("Uninstall", mock.Anything, mock.Anything).Return(nil, originalError)
8799
ac.On("Upgrade", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, originalError)
@@ -93,6 +105,10 @@ func TestActionClientErrorTranslation(t *testing.T) {
93105
_, err := wrappedAc.Get("something")
94106
assert.Equal(t, expectedErr, err, "expected Get() to return translated error")
95107

108+
// History
109+
_, err = wrappedAc.History("something")
110+
assert.Equal(t, expectedErr, err, "expected History() to return translated error")
111+
96112
// Install
97113
_, err = wrappedAc.Install("something", "somethingelse", nil, nil)
98114
assert.Equal(t, expectedErr, err, "expected Install() to return translated error")

internal/controllers/clusterextension_controller.go

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ type Applier interface {
8484
}
8585

8686
type InstalledBundleGetter interface {
87-
GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error)
87+
GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*InstalledBundle, error)
8888
}
8989

9090
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch;update;patch
@@ -206,19 +206,22 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
206206
installedBundle, err := r.InstalledBundleGetter.GetInstalledBundle(ctx, ext)
207207
if err != nil {
208208
setInstallStatus(ext, nil)
209-
// TODO: use Installed=Unknown
210-
setInstalledStatusConditionFailed(ext, err.Error())
211-
setStatusProgressing(ext, err)
209+
setInstalledStatusConditionUnknown(ext, err.Error())
210+
setStatusProgressing(ext, errors.New("retrying to get installed bundle"))
212211
return ctrl.Result{}, err
213212
}
214213

215214
// run resolution
216215
l.Info("resolving bundle")
217-
resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.Resolver.Resolve(ctx, ext, installedBundle)
216+
var bm *ocv1alpha1.BundleMetadata
217+
if installedBundle != nil {
218+
bm = &installedBundle.BundleMetadata
219+
}
220+
resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.Resolver.Resolve(ctx, ext, bm)
218221
if err != nil {
219222
// Note: We don't distinguish between resolution-specific errors and generic errors
220-
setInstallStatus(ext, nil)
221223
setStatusProgressing(ext, err)
224+
setInstalledStatusFromBundle(ext, installedBundle)
222225
ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonFailed, err.Error())
223226
return ctrl.Result{}, err
224227
}
@@ -255,6 +258,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
255258
// installed since we intend for the progressing condition to replace the resolved condition
256259
// and will be removing the .status.resolution field from the ClusterExtension status API
257260
setStatusProgressing(ext, wrapErrorWithResolutionInfo(resolvedBundleMetadata, err))
261+
setInstalledStatusFromBundle(ext, installedBundle)
258262
return ctrl.Result{}, err
259263
}
260264

@@ -268,9 +272,10 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
268272
}
269273

270274
storeLbls := map[string]string{
271-
labels.BundleNameKey: resolvedBundle.Name,
272-
labels.PackageNameKey: resolvedBundle.Package,
273-
labels.BundleVersionKey: resolvedBundleVersion.String(),
275+
labels.BundleNameKey: resolvedBundle.Name,
276+
labels.PackageNameKey: resolvedBundle.Package,
277+
labels.BundleVersionKey: resolvedBundleVersion.String(),
278+
labels.BundleReferenceKey: resolvedBundle.Image,
274279
}
275280

276281
l.Info("applying bundle contents")
@@ -286,18 +291,17 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
286291
managedObjs, _, err := r.Applier.Apply(ctx, unpackResult.Bundle, ext, objLbls, storeLbls)
287292
if err != nil {
288293
setStatusProgressing(ext, wrapErrorWithResolutionInfo(resolvedBundleMetadata, err))
289-
// If bundle is not already installed, set Installed status condition to False
290-
if installedBundle == nil {
291-
setInstalledStatusConditionFailed(ext, err.Error())
292-
}
294+
// Now that we're actually trying to install, use the error
295+
setInstalledStatusFromBundle(ext, installedBundle)
293296
return ctrl.Result{}, err
294297
}
295298

296-
installStatus := &ocv1alpha1.ClusterExtensionInstallStatus{
297-
Bundle: resolvedBundleMetadata,
299+
newInstalledBundle := &InstalledBundle{
300+
BundleMetadata: resolvedBundleMetadata,
301+
Image: resolvedBundle.Image,
298302
}
299-
setInstallStatus(ext, installStatus)
300-
setInstalledStatusConditionSuccess(ext, fmt.Sprintf("Installed bundle %s successfully", resolvedBundle.Image))
303+
// Successful install
304+
setInstalledStatusFromBundle(ext, newInstalledBundle)
301305

302306
l.Info("watching managed objects")
303307
cache, err := r.Manager.Get(ctx, ext)
@@ -466,32 +470,45 @@ type DefaultInstalledBundleGetter struct {
466470
helmclient.ActionClientGetter
467471
}
468472

469-
func (d *DefaultInstalledBundleGetter) GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error) {
473+
type InstalledBundle struct {
474+
ocv1alpha1.BundleMetadata
475+
Image string
476+
}
477+
478+
func (d *DefaultInstalledBundleGetter) GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*InstalledBundle, error) {
470479
cl, err := d.ActionClientFor(ctx, ext)
471480
if err != nil {
472481
return nil, err
473482
}
474483

475-
rel, err := cl.Get(ext.GetName())
484+
relhis, err := cl.History(ext.GetName())
476485
if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
477486
return nil, err
478487
}
479-
if rel == nil {
488+
if len(relhis) == 0 {
480489
return nil, nil
481490
}
482491

483-
switch rel.Info.Status {
484-
case release.StatusUnknown:
485-
return nil, fmt.Errorf("installation status is unknown")
486-
case release.StatusDeployed, release.StatusUninstalled, release.StatusSuperseded, release.StatusFailed:
487-
case release.StatusUninstalling, release.StatusPendingInstall, release.StatusPendingRollback, release.StatusPendingUpgrade:
488-
return nil, fmt.Errorf("installation is still pending: %s", rel.Info.Status)
489-
default:
490-
return nil, fmt.Errorf("unknown installation status: %s", rel.Info.Status)
492+
// relhis[0].Info.Status is the status of the most recent install attempt.
493+
// But we need to look for the most-recent _Deployed_ release
494+
for _, rel := range relhis {
495+
if rel.Info != nil && rel.Info.Status == release.StatusDeployed {
496+
// If there are blank values, we should consider this as not installed
497+
if n, ok := rel.Labels[labels.BundleNameKey]; !ok || n == "" {
498+
return nil, nil
499+
}
500+
if v, ok := rel.Labels[labels.BundleVersionKey]; !ok || v == "" {
501+
return nil, nil
502+
}
503+
// Not checking BundleReferenceKey, as it's new; upgrade test would fail
504+
return &InstalledBundle{
505+
BundleMetadata: ocv1alpha1.BundleMetadata{
506+
Name: rel.Labels[labels.BundleNameKey],
507+
Version: rel.Labels[labels.BundleVersionKey],
508+
},
509+
Image: rel.Labels[labels.BundleReferenceKey],
510+
}, nil
511+
}
491512
}
492-
493-
return &ocv1alpha1.BundleMetadata{
494-
Name: rel.Labels[labels.BundleNameKey],
495-
Version: rel.Labels[labels.BundleVersionKey],
496-
}, nil
513+
return nil, nil
497514
}

internal/controllers/clusterextension_controller_test.go

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212
"github.com/google/go-cmp/cmp/cmpopts"
1313
"github.com/stretchr/testify/assert"
1414
"github.com/stretchr/testify/require"
15+
"helm.sh/helm/v3/pkg/chart"
16+
"helm.sh/helm/v3/pkg/release"
17+
"helm.sh/helm/v3/pkg/storage/driver"
1518
apimeta "k8s.io/apimachinery/pkg/api/meta"
1619
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1720
"k8s.io/apimachinery/pkg/types"
@@ -21,12 +24,14 @@ import (
2124
crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
2225
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2326

27+
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2428
"github.com/operator-framework/operator-registry/alpha/declcfg"
2529

2630
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
2731
"github.com/operator-framework/operator-controller/internal/conditionsets"
2832
"github.com/operator-framework/operator-controller/internal/controllers"
2933
"github.com/operator-framework/operator-controller/internal/finalizers"
34+
"github.com/operator-framework/operator-controller/internal/labels"
3035
"github.com/operator-framework/operator-controller/internal/resolve"
3136
"github.com/operator-framework/operator-controller/internal/rukpak/source"
3237
)
@@ -392,7 +397,10 @@ func TestClusterExtensionApplierFailsWithBundleInstalled(t *testing.T) {
392397
cache: &MockManagedContentCache{},
393398
}
394399
reconciler.InstalledBundleGetter = &MockInstalledBundleGetter{
395-
bundle: &ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
400+
bundle: &controllers.InstalledBundle{
401+
BundleMetadata: ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
402+
Image: "quay.io/operatorhubio/prometheus@fake1.0.0",
403+
},
396404
}
397405
reconciler.Applier = &MockApplier{
398406
objs: []client.Object{},
@@ -745,7 +753,10 @@ func TestClusterExtensionDeleteFinalizerFails(t *testing.T) {
745753
cache: &MockManagedContentCache{},
746754
}
747755
reconciler.InstalledBundleGetter = &MockInstalledBundleGetter{
748-
bundle: &ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
756+
bundle: &controllers.InstalledBundle{
757+
BundleMetadata: ocv1alpha1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"},
758+
Image: "quay.io/operatorhubio/prometheus@fake1.0.0",
759+
},
749760
}
750761
err = reconciler.Finalizers.Register(fakeFinalizer, finalizers.FinalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
751762
return crfinalizer.Result{}, errors.New(finalizersMessage)
@@ -1400,3 +1411,133 @@ func TestSetDeprecationStatus(t *testing.T) {
14001411
})
14011412
}
14021413
}
1414+
1415+
type MockActionGetter struct {
1416+
description string
1417+
rels []*release.Release
1418+
err error
1419+
expectedBundle *controllers.InstalledBundle
1420+
expectedError error
1421+
}
1422+
1423+
func (mag *MockActionGetter) ActionClientFor(ctx context.Context, obj client.Object) (helmclient.ActionInterface, error) {
1424+
return mag, nil
1425+
}
1426+
1427+
func (mag *MockActionGetter) Get(name string, opts ...helmclient.GetOption) (*release.Release, error) {
1428+
return nil, nil
1429+
}
1430+
1431+
// This is the function we are really testing
1432+
func (mag *MockActionGetter) History(name string, opts ...helmclient.HistoryOption) ([]*release.Release, error) {
1433+
return mag.rels, mag.err
1434+
}
1435+
1436+
func (mag *MockActionGetter) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.InstallOption) (*release.Release, error) {
1437+
return nil, nil
1438+
}
1439+
1440+
func (mag *MockActionGetter) Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...helmclient.UpgradeOption) (*release.Release, error) {
1441+
return nil, nil
1442+
}
1443+
1444+
func (mag *MockActionGetter) Uninstall(name string, opts ...helmclient.UninstallOption) (*release.UninstallReleaseResponse, error) {
1445+
return nil, nil
1446+
}
1447+
1448+
func (mag *MockActionGetter) Reconcile(rel *release.Release) error {
1449+
return nil
1450+
}
1451+
1452+
func TestGetInstalledBundleHistory(t *testing.T) {
1453+
getter := controllers.DefaultInstalledBundleGetter{}
1454+
1455+
ext := ocv1alpha1.ClusterExtension{
1456+
ObjectMeta: metav1.ObjectMeta{
1457+
Name: "test-ext",
1458+
},
1459+
}
1460+
1461+
mag := []MockActionGetter{
1462+
{
1463+
"No return",
1464+
nil, nil,
1465+
nil, nil,
1466+
},
1467+
{
1468+
"ErrReleaseNotFound (special case)",
1469+
nil, driver.ErrReleaseNotFound,
1470+
nil, nil,
1471+
},
1472+
{
1473+
"Error from History",
1474+
nil, fmt.Errorf("generic error"),
1475+
nil, fmt.Errorf("generic error"),
1476+
},
1477+
{
1478+
"One item in history",
1479+
[]*release.Release{
1480+
{
1481+
Name: "test-ext",
1482+
Info: &release.Info{
1483+
Status: release.StatusDeployed,
1484+
},
1485+
Labels: map[string]string{
1486+
labels.BundleNameKey: "test-ext",
1487+
labels.BundleVersionKey: "1.0",
1488+
labels.BundleReferenceKey: "bundle-ref",
1489+
},
1490+
},
1491+
}, nil,
1492+
&controllers.InstalledBundle{
1493+
BundleMetadata: ocv1alpha1.BundleMetadata{
1494+
Name: "test-ext",
1495+
Version: "1.0",
1496+
},
1497+
Image: "bundle-ref",
1498+
}, nil,
1499+
},
1500+
{
1501+
"Two items in history",
1502+
[]*release.Release{
1503+
{
1504+
Name: "test-ext",
1505+
Info: &release.Info{
1506+
Status: release.StatusFailed,
1507+
},
1508+
Labels: map[string]string{
1509+
labels.BundleNameKey: "test-ext",
1510+
labels.BundleVersionKey: "2.0",
1511+
labels.BundleReferenceKey: "bundle-ref-2",
1512+
},
1513+
},
1514+
{
1515+
Name: "test-ext",
1516+
Info: &release.Info{
1517+
Status: release.StatusDeployed,
1518+
},
1519+
Labels: map[string]string{
1520+
labels.BundleNameKey: "test-ext",
1521+
labels.BundleVersionKey: "1.0",
1522+
labels.BundleReferenceKey: "bundle-ref-1",
1523+
},
1524+
},
1525+
}, nil,
1526+
&controllers.InstalledBundle{
1527+
BundleMetadata: ocv1alpha1.BundleMetadata{
1528+
Name: "test-ext",
1529+
Version: "1.0",
1530+
},
1531+
Image: "bundle-ref-1",
1532+
}, nil,
1533+
},
1534+
}
1535+
1536+
for _, tst := range mag {
1537+
t.Log(tst.description)
1538+
getter.ActionClientGetter = &tst
1539+
md, err := getter.GetInstalledBundle(context.Background(), &ext)
1540+
require.Equal(t, tst.expectedError, err)
1541+
require.Equal(t, tst.expectedBundle, md)
1542+
}
1543+
}

0 commit comments

Comments
 (0)