Skip to content

Commit

Permalink
Merge pull request #217 from jtyr/jtyr-mp
Browse files Browse the repository at this point in the history
Adding support for managementPolicies (fixes #209)
  • Loading branch information
lsviben authored Apr 8, 2024
2 parents a332970 + 5d2fcbe commit d32ead0
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 55 deletions.
7 changes: 7 additions & 0 deletions cmd/provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func main() {
syncInterval = app.Flag("sync", "How often all resources will be double-checked for drift from the desired state.").Short('s').Default("1h").Duration()
pollInterval = app.Flag("poll", "How often individual resources will be checked for drift from the desired state").Default("10m").Duration()
maxReconcileRate = app.Flag("max-reconcile-rate", "The global maximum rate per second at which resources may checked for drift from the desired state.").Default("100").Int()

enableManagementPolicies = app.Flag("enable-management-policies", "Enable support for Management Policies.").Default("true").Envar("ENABLE_MANAGEMENT_POLICIES").Bool()
)
kingpin.MustParse(app.Parse(os.Args[1:]))

Expand Down Expand Up @@ -96,6 +98,11 @@ func main() {
Features: &feature.Flags{},
}

if *enableManagementPolicies {
o.Features.Enable(feature.EnableBetaManagementPolicies)
log.Info("Beta feature enabled", "flag", feature.EnableBetaManagementPolicies)
}

kingpin.FatalIfError(template.Setup(mgr, o, *timeout), "Cannot setup Template controllers")
kingpin.FatalIfError(mgr.Start(ctrl.SetupSignalHandler()), "Cannot start controller manager")
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/controller/release/observe.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import (
"fmt"
"strings"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"

"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/release"
Expand Down Expand Up @@ -59,7 +61,7 @@ func generateObservation(in *release.Release) v1beta1.ReleaseObservation {
}

// isUpToDate checks whether desired spec up to date with the observed state for a given release
func isUpToDate(ctx context.Context, kube client.Client, in *v1beta1.ReleaseParameters, observed *release.Release, s v1beta1.ReleaseStatus) (bool, error) {
func isUpToDate(ctx context.Context, kube client.Client, spec *v1beta1.ReleaseSpec, observed *release.Release, s v1beta1.ReleaseStatus) (bool, error) { // nolint:gocyclo
if observed.Info == nil {
return false, errors.New(errReleaseInfoNilInObservedRelease)
}
Expand All @@ -77,9 +79,20 @@ func isUpToDate(ctx context.Context, kube client.Client, in *v1beta1.ReleasePara
if ocm == nil {
return false, errors.New(errChartMetaNilInObservedRelease)
}

in := spec.ForProvider

if in.Chart.Name != ocm.Name {
return false, nil
}

mp := sets.New[xpv1.ManagementAction](spec.ManagementPolicies...)

if len(mp) != 0 && !mp.HasAny(xpv1.ManagementActionUpdate, xpv1.ManagementActionAll) {
// Treated as up-to-date as we don't update or create the resource
return true, nil
}

if in.Chart.Version != ocm.Version && in.Chart.Version != devel {
return false, nil
}
Expand Down
198 changes: 148 additions & 50 deletions pkg/controller/release/observe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -92,7 +93,7 @@ func Test_generateObservation(t *testing.T) {
func Test_isUpToDate(t *testing.T) {
type args struct {
kube client.Client
in *v1beta1.ReleaseParameters
spec *v1beta1.ReleaseSpec
observed *release.Release
}
type want struct {
Expand Down Expand Up @@ -162,15 +163,17 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("invalid-yaml"),
Object: nil,
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("invalid-yaml"),
Object: nil,
},
},
},
},
Expand Down Expand Up @@ -200,14 +203,16 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: "another-chart",
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: "another-chart",
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
},
},
},
},
Expand All @@ -233,14 +238,16 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: "another-version",
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: "another-version",
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
},
},
},
},
Expand All @@ -266,14 +273,101 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("keyA: valX"),
},
},
},
},
observed: &release.Release{
Info: &release.Info{},
Chart: &chart.Chart{
Raw: nil,
Metadata: &chart.Metadata{
Name: testChart,
Version: testVersion,
},
},
Config: testReleaseConfig,
},
},
want: want{
out: false,
err: nil,
},
},
"NotUpToDate_ConfigIsDifferent_ManagementPolicies_DoesApply": {
args: args{
kube: &test.MockClient{
MockGet: nil,
},
spec: &v1beta1.ReleaseSpec{
ResourceSpec: xpv1.ResourceSpec{
ManagementPolicies: []xpv1.ManagementAction{
xpv1.ManagementActionCreate,
xpv1.ManagementActionDelete,
xpv1.ManagementActionObserve,
},
},
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("keyA: valX"),
},
},
},
},
observed: &release.Release{
Info: &release.Info{},
Chart: &chart.Chart{
Raw: nil,
Metadata: &chart.Metadata{
Name: testChart,
Version: testVersion,
},
},
Config: testReleaseConfig,
},
},
want: want{
out: true,
err: nil,
},
},
"NotUpToDate_ConfigIsDifferent_ManagementPolicies_DoesNotApply": {
args: args{
kube: &test.MockClient{
MockGet: nil,
},
spec: &v1beta1.ReleaseSpec{
ResourceSpec: xpv1.ResourceSpec{
ManagementPolicies: []xpv1.ManagementAction{
xpv1.ManagementActionCreate,
xpv1.ManagementActionDelete,
xpv1.ManagementActionObserve,
xpv1.ManagementActionUpdate,
},
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("keyA: valX"),
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte("keyA: valX"),
},
},
},
},
Expand All @@ -299,14 +393,16 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
},
},
},
},
Expand All @@ -332,14 +428,16 @@ func Test_isUpToDate(t *testing.T) {
kube: &test.MockClient{
MockGet: nil,
},
in: &v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
spec: &v1beta1.ReleaseSpec{
ForProvider: v1beta1.ReleaseParameters{
Chart: v1beta1.ChartSpec{
Name: testChart,
Version: testVersion,
},
ValuesSpec: v1beta1.ValuesSpec{
Values: runtime.RawExtension{
Raw: []byte(testReleaseConfigStr),
},
},
},
},
Expand All @@ -364,7 +462,7 @@ func Test_isUpToDate(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got, gotErr := isUpToDate(context.Background(), tc.args.kube, tc.args.in, tc.args.observed, v1beta1.ReleaseStatus{})
got, gotErr := isUpToDate(context.Background(), tc.args.kube, tc.args.spec, tc.args.observed, v1beta1.ReleaseStatus{})
if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" {
t.Fatalf("isUpToDate(...): -want error, +got error: %s", diff)
}
Expand Down
18 changes: 14 additions & 4 deletions pkg/controller/release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/controller"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/crossplane-runtime/pkg/feature"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"
Expand Down Expand Up @@ -91,8 +92,7 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error
name := managed.ControllerName(v1beta1.ReleaseGroupKind)
cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())}

r := managed.NewReconciler(mgr,
resource.ManagedKind(v1beta1.ReleaseGroupVersionKind),
reconcilerOptions := []managed.ReconcilerOption{
managed.WithExternalConnecter(&connector{
client: mgr.GetClient(),
logger: o.Logger,
Expand All @@ -110,7 +110,17 @@ func Setup(mgr ctrl.Manager, o controller.Options, timeout time.Duration) error
managed.WithLogger(o.Logger.WithValues("controller", name)),
managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
managed.WithTimeout(timeout),
managed.WithConnectionPublishers(cps...))
managed.WithConnectionPublishers(cps...),
}

if o.Features.Enabled(feature.EnableBetaManagementPolicies) {
reconcilerOptions = append(reconcilerOptions, managed.WithManagementPolicies())
}

r := managed.NewReconciler(mgr,
resource.ManagedKind(v1beta1.ReleaseGroupVersionKind),
reconcilerOptions...,
)

return ctrl.NewControllerManagedBy(mgr).
Named(name).
Expand Down Expand Up @@ -291,7 +301,7 @@ func (e *helmExternal) Observe(ctx context.Context, mg resource.Managed) (manage
return managed.ExternalObservation{ResourceExists: true}, nil
}

s, err := isUpToDate(ctx, e.localKube, &cr.Spec.ForProvider, rel, cr.Status)
s, err := isUpToDate(ctx, e.localKube, &cr.Spec, rel, cr.Status)
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, errFailedToCheckIfUpToDate)
}
Expand Down

0 comments on commit d32ead0

Please sign in to comment.