From 2523600ae9338f7b9e7aadb488d6e47277acd822 Mon Sep 17 00:00:00 2001 From: Andrew Pantuso Date: Mon, 31 Oct 2022 12:10:04 -0400 Subject: [PATCH] feat: add status api for ReferenceAddon --- .../v1alpha1/referenceaddon_types.go | 40 ++++ internal/controllers/helpers.go | 48 +++++ internal/controllers/options.go | 17 +- internal/controllers/phase/phase.go | 81 ++++++-- .../phase_apply_network_policies.go | 38 +++- .../phase_apply_network_policies_test.go | 30 +-- .../phase_send_dummy_metrics_test.go | 2 +- .../phase_simulate_reconciliation.go | 68 ------- .../phase_simulate_reconciliation_test.go | 60 ------ internal/controllers/phase_uninstall.go | 10 +- .../controllers/reference_addon_controller.go | 173 ++++++++++++++---- 11 files changed, 357 insertions(+), 210 deletions(-) delete mode 100644 internal/controllers/phase_simulate_reconciliation.go delete mode 100644 internal/controllers/phase_simulate_reconciliation_test.go diff --git a/apis/reference/v1alpha1/referenceaddon_types.go b/apis/reference/v1alpha1/referenceaddon_types.go index b96e671f..86ea0cc0 100644 --- a/apis/reference/v1alpha1/referenceaddon_types.go +++ b/apis/reference/v1alpha1/referenceaddon_types.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -14,6 +15,39 @@ type ReferenceAddonStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } +type ReferenceAddonCondition string + +func (c ReferenceAddonCondition) String() string { + return string(c) +} + +const ( + ReferenceAddonConditionAvailable ReferenceAddonCondition = "Available" +) + +type ReferenceAddonAvailableReason string + +func (r ReferenceAddonAvailableReason) String() string { + return string(r) +} + +func (r ReferenceAddonAvailableReason) Status() metav1.ConditionStatus { + switch r { + case ReferenceAddonAvailableReasonReady: + return "True" + case ReferenceAddonAvailableReasonPending: + return "False" + default: + return "Unknown" + } +} + +const ( + ReferenceAddonAvailableReasonReady ReferenceAddonAvailableReason = "Ready" + ReferenceAddonAvailableReasonPending ReferenceAddonAvailableReason = "Pending" + ReferenceAddonAvailableReasonUninstalling ReferenceAddonAvailableReason = "Uninstalling" +) + // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" @@ -25,6 +59,12 @@ type ReferenceAddon struct { Status ReferenceAddonStatus `json:"status,omitempty"` } +func (a *ReferenceAddon) HasConditionAvailable() bool { + condT := ReferenceAddonConditionAvailable.String() + + return meta.FindStatusCondition(a.Status.Conditions, condT) != nil +} + // ReferenceAddonList contains a list of ReferenceAddons // +kubebuilder:object:root=true type ReferenceAddonList struct { diff --git a/internal/controllers/helpers.go b/internal/controllers/helpers.go index 916544ab..1c81b16f 100644 --- a/internal/controllers/helpers.go +++ b/internal/controllers/helpers.go @@ -1,8 +1,20 @@ package controllers import ( + "context" "errors" "fmt" + "strings" + + refv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" ) var ErrEmptyOptionValue = errors.New("empty option value") @@ -26,3 +38,39 @@ func boolPtr(b bool) *bool { func stringPtr(s string) *string { return &s } + +func newAvailableCondition(reason refv1alpha1.ReferenceAddonAvailableReason, msg string) metav1.Condition { + return metav1.Condition{ + Type: refv1alpha1.ReferenceAddonConditionAvailable.String(), + Status: reason.Status(), + Reason: reason.String(), + Message: msg, + LastTransitionTime: metav1.Now(), + } +} + +func enqueueObject(obj types.NamespacedName) source.Func { + return func(_ context.Context, _ handler.EventHandler, q workqueue.RateLimitingInterface, _ ...predicate.Predicate) error { + q.Add(reconcile.Request{ + NamespacedName: obj, + }) + + return nil + } +} + +func hasNamePrefix(pfx string) predicate.Funcs { + return predicate.NewPredicateFuncs( + func(obj client.Object) bool { + return strings.HasPrefix(obj.GetName(), pfx) + }, + ) +} + +func hasName(name string) predicate.Funcs { + return predicate.NewPredicateFuncs( + func(obj client.Object) bool { + return obj.GetName() == name + }, + ) +} diff --git a/internal/controllers/options.go b/internal/controllers/options.go index 86f3fecb..0c06c927 100644 --- a/internal/controllers/options.go +++ b/internal/controllers/options.go @@ -3,6 +3,7 @@ package controllers import ( "github.com/go-logr/logr" netv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type WithLog struct{ Log logr.Logger } @@ -15,10 +16,6 @@ func (w WithLog) ConfigurePhaseApplyNetworkPolicies(c *PhaseApplyNetworkPolicies c.Log = w.Log } -func (w WithLog) ConfigurePhaseSimulateReconciliation(c *PhaseSimulateReconciliationConfig) { - c.Log = w.Log -} - func (w WithLog) ConfigurePhaseUninstall(c *PhaseUninstallConfig) { c.Log = w.Log } @@ -91,10 +88,20 @@ func (w WithNamespace) ConfigureListCSVs(c *ListCSVsConfig) { c.Namespace = string(w) } +type WithOwner struct{ Owner metav1.Object } + +func (w WithOwner) ConfigureApplyNetworkPolicies(c *ApplyNetorkPoliciesConfig) { + c.Owner = w.Owner +} + type WithPolicies []netv1.NetworkPolicy func (w WithPolicies) ConfigurePhaseApplyNetworkPolicies(c *PhaseApplyNetworkPoliciesConfig) { - c.Policies = []netv1.NetworkPolicy(w) + c.Policies = append(c.Policies, w...) +} + +func (w WithPolicies) ConfigureApplyNetworkPolicies(c *ApplyNetorkPoliciesConfig) { + c.Policies = append(c.Policies, w...) } type WithPrefix string diff --git a/internal/controllers/phase/phase.go b/internal/controllers/phase/phase.go index b25c44ea..38c9b98f 100644 --- a/internal/controllers/phase/phase.go +++ b/internal/controllers/phase/phase.go @@ -3,7 +3,8 @@ package phase import ( "context" - "k8s.io/apimachinery/pkg/types" + refv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Phase interface { @@ -11,7 +12,7 @@ type Phase interface { } type Request struct { - Object types.NamespacedName + Addon refv1alpha1.ReferenceAddon Params RequestParameters } @@ -74,46 +75,61 @@ type RequestParametersOption interface { ConfigureRequestParameters(*RequestParametersConfig) } -func Success() Result { +func Success(opts ...ResultOption) Result { + var cfg ResultConfig + + cfg.Option(opts...) + return Result{ + cfg: cfg, status: StatusSuccess, } } -func Blocking() Result { +func Blocking(opts ...ResultOption) Result { + var cfg ResultConfig + + cfg.Option(opts...) + return Result{ - status: StatusSuccess, - blocking: true, + cfg: cfg, + status: StatusBlocking, } } -func Failure(msg string) Result { +func Failure(msg string, opts ...ResultOption) Result { + var cfg ResultConfig + + cfg.Option(opts...) + return Result{ + cfg: cfg, status: StatusFailure, failureMsg: msg, } } -func Error(err error) Result { +func Error(err error, opts ...ResultOption) Result { + var cfg ResultConfig + + cfg.Option(opts...) + return Result{ + cfg: cfg, status: StatusError, err: err, } } type Result struct { + cfg ResultConfig err error failureMsg string status Status - blocking bool } -func (r Result) IsSuccess() bool { - return r.status == StatusSuccess -} - -func (r Result) IsBlocking() bool { - return r.blocking +func (r Result) Status() Status { + return r.status } func (r Result) FailureMessage() string { @@ -124,10 +140,39 @@ func (r Result) Error() error { return r.err } +func (r Result) Conditions() []metav1.Condition { + return r.cfg.Conditions +} + type Status string +func (s Status) String() string { + return string(s) +} + const ( - StatusSuccess = "success" - StatusFailure = "failure" - StatusError = "error" + StatusBlocking Status = "blocking" + StatusError Status = "error" + StatusFailure Status = "failure" + StatusSuccess Status = "success" ) + +type ResultConfig struct { + Conditions []metav1.Condition +} + +func (c *ResultConfig) Option(opts ...ResultOption) { + for _, opt := range opts { + opt.ConfigureResult(c) + } +} + +type ResultOption interface { + ConfigureResult(c *ResultConfig) +} + +type WithConditions []metav1.Condition + +func (w WithConditions) ConfigureResult(c *ResultConfig) { + c.Conditions = append(c.Conditions, w...) +} diff --git a/internal/controllers/phase_apply_network_policies.go b/internal/controllers/phase_apply_network_policies.go index a71df2ba..8d8abaed 100644 --- a/internal/controllers/phase_apply_network_policies.go +++ b/internal/controllers/phase_apply_network_policies.go @@ -47,7 +47,7 @@ func (p *PhaseApplyNetworkPolicies) Execute(ctx context.Context, req phase.Reque return p.ensureNetworkPoliciesRemoved(ctx) } - return p.ensureNetworkPoliciesApplied(ctx) + return p.ensureNetworkPoliciesApplied(ctx, req) } func (p *PhaseApplyNetworkPolicies) ensureNetworkPoliciesRemoved(ctx context.Context) phase.Result { @@ -62,10 +62,10 @@ func (p *PhaseApplyNetworkPolicies) ensureNetworkPoliciesRemoved(ctx context.Con return phase.Success() } -func (p *PhaseApplyNetworkPolicies) ensureNetworkPoliciesApplied(ctx context.Context) phase.Result { +func (p *PhaseApplyNetworkPolicies) ensureNetworkPoliciesApplied(ctx context.Context, req phase.Request) phase.Result { p.cfg.Log.Info("applying NetworkPolicies", "count", len(p.cfg.Policies)) - if err := p.client.ApplyNetworkPolicies(ctx, p.cfg.Policies...); err != nil { + if err := p.client.ApplyNetworkPolicies(ctx, WithOwner{Owner: &req.Addon}, WithPolicies(p.cfg.Policies)); err != nil { return phase.Error(fmt.Errorf("applying NetworkPolicies: %w", err)) } @@ -97,7 +97,7 @@ type PhaseApplyNetworkPoliciesOption interface { } type NetworkPolicyClient interface { - ApplyNetworkPolicies(ctx context.Context, policies ...netv1.NetworkPolicy) error + ApplyNetworkPolicies(ctx context.Context, opts ...ApplyNetorkPoliciesOption) error RemoveNetworkPolicies(ctx context.Context, policies ...netv1.NetworkPolicy) error } @@ -111,13 +111,23 @@ type NetworkPolicyClientImpl struct { client client.Client } -func (c *NetworkPolicyClientImpl) ApplyNetworkPolicies(ctx context.Context, policies ...netv1.NetworkPolicy) error { +func (c *NetworkPolicyClientImpl) ApplyNetworkPolicies(ctx context.Context, opts ...ApplyNetorkPoliciesOption) error { ctx, cancel := context.WithCancel(ctx) defer cancel() + var cfg ApplyNetorkPoliciesConfig + + cfg.Option(opts...) + var finalErr error - for _, policy := range policies { + for _, policy := range cfg.Policies { + if cfg.Owner != nil { + if err := ctrl.SetControllerReference(cfg.Owner, &policy, c.client.Scheme()); err != nil { + return fmt.Errorf("setting controller reference: %w", err) + } + } + if err := c.createOrUpdatePolicy(ctx, policy); err != nil { multierr.AppendInto(&finalErr, fmt.Errorf("creating/updating NetworkPolicy %q: %w", policy.Name, err)) } @@ -126,6 +136,21 @@ func (c *NetworkPolicyClientImpl) ApplyNetworkPolicies(ctx context.Context, poli return finalErr } +type ApplyNetorkPoliciesConfig struct { + Owner metav1.Object + Policies []netv1.NetworkPolicy +} + +func (c *ApplyNetorkPoliciesConfig) Option(opts ...ApplyNetorkPoliciesOption) { + for _, opt := range opts { + opt.ConfigureApplyNetworkPolicies(c) + } +} + +type ApplyNetorkPoliciesOption interface { + ConfigureApplyNetworkPolicies(c *ApplyNetorkPoliciesConfig) +} + func (c *NetworkPolicyClientImpl) createOrUpdatePolicy(ctx context.Context, policy netv1.NetworkPolicy) error { actualPolicy := &netv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ @@ -136,6 +161,7 @@ func (c *NetworkPolicyClientImpl) createOrUpdatePolicy(ctx context.Context, poli _, err := ctrl.CreateOrUpdate(ctx, c.client, actualPolicy, func() error { actualPolicy.Labels = labels.Merge(actualPolicy.Labels, policy.Labels) + actualPolicy.OwnerReferences = policy.OwnerReferences actualPolicy.Spec = policy.Spec return nil diff --git a/internal/controllers/phase_apply_network_policies_test.go b/internal/controllers/phase_apply_network_policies_test.go index 6f82d271..c3d69b13 100644 --- a/internal/controllers/phase_apply_network_policies_test.go +++ b/internal/controllers/phase_apply_network_policies_test.go @@ -66,21 +66,23 @@ func TestPhaseApplyNetworkPolicies(t *testing.T) { var m NetworkPolicyClientMock - argList := make([]interface{}, 0, 1+len(tc.Policies)) - - argList = append(argList, mock.Anything) - - for _, p := range tc.Policies { - argList = append(argList, p) - } - switch val := tc.ApplyNetworkPolicy; { case val == nil: case *val == true: + argList := []interface{}{mock.Anything, mock.Anything, WithPolicies(tc.Policies)} + m. On("ApplyNetworkPolicies", argList...). Return(nil) case *val == false: + argList := make([]interface{}, 0, 1+len(tc.Policies)) + + argList = append(argList, mock.Anything) + + for _, p := range tc.Policies { + argList = append(argList, p) + } + m. On("RemoveNetworkPolicies", argList...). Return(nil) @@ -98,7 +100,7 @@ func TestPhaseApplyNetworkPolicies(t *testing.T) { }) require.NoError(t, res.Error()) - assert.True(t, res.IsSuccess()) + assert.Equal(t, phase.StatusSuccess, res.Status()) m.AssertExpectations(t) }) @@ -109,13 +111,13 @@ type NetworkPolicyClientMock struct { mock.Mock } -func (m *NetworkPolicyClientMock) ApplyNetworkPolicies(ctx context.Context, policies ...netv1.NetworkPolicy) error { - argList := make([]interface{}, 0, 1+len(policies)) +func (m *NetworkPolicyClientMock) ApplyNetworkPolicies(ctx context.Context, opts ...ApplyNetorkPoliciesOption) error { + argList := make([]interface{}, 0, 1+len(opts)) argList = append(argList, ctx) - for _, p := range policies { - argList = append(argList, p) + for _, o := range opts { + argList = append(argList, o) } args := m.Called(argList...) @@ -241,7 +243,7 @@ func TestNetworkPolicyClientImpl_ApplyNetworkPolicies(t *testing.T) { require.NoError(t, npClient.ApplyNetworkPolicies( context.Background(), - tc.DesiredPolicies..., + WithPolicies(tc.DesiredPolicies), ), ) diff --git a/internal/controllers/phase_send_dummy_metrics_test.go b/internal/controllers/phase_send_dummy_metrics_test.go index 01167ebb..4e5eee08 100644 --- a/internal/controllers/phase_send_dummy_metrics_test.go +++ b/internal/controllers/phase_send_dummy_metrics_test.go @@ -47,7 +47,7 @@ func TestPhaseSendDummyMetrics(t *testing.T) { res := p.Execute(context.Background(), phase.Request{}) require.NoError(t, res.Error()) - assert.True(t, res.IsSuccess()) + assert.Equal(t, phase.StatusSuccess, res.Status()) }) } } diff --git a/internal/controllers/phase_simulate_reconciliation.go b/internal/controllers/phase_simulate_reconciliation.go deleted file mode 100644 index fdd9edba..00000000 --- a/internal/controllers/phase_simulate_reconciliation.go +++ /dev/null @@ -1,68 +0,0 @@ -package controllers - -import ( - "context" - "strings" - - "github.com/go-logr/logr" - "github.com/openshift/reference-addon/internal/controllers/phase" -) - -func NewPhaseSimulateReconciliation(opts ...PhaseSimulateReconciliationOption) *PhaseSimulateReconciliation { - var cfg PhaseSimulateReconciliationConfig - - cfg.Option(opts...) - cfg.Default() - - return &PhaseSimulateReconciliation{ - cfg: cfg, - } -} - -type PhaseSimulateReconciliation struct { - cfg PhaseSimulateReconciliationConfig -} - -func (p *PhaseSimulateReconciliation) Execute(ctx context.Context, req phase.Request) phase.Result { - log := p.cfg.Log.WithValues("addon", req.Object.String()) - - applyNetworkPolicies, _ := req.Params.GetApplyNetworkPolicies() - size, _ := req.Params.GetSize() - - log.Info( - "reconciling with addon parameters", - "ApplyNetworkPolicies", applyNetworkPolicies, - "Size", size, - ) - - // dummy code to indicate reconciliation of the reference-addon object - if strings.HasPrefix(req.Object.Name, "redhat-") { - log.Info("reconciling for a reference addon object prefixed by redhat- ") - } else if strings.HasPrefix(req.Object.Name, "reference-addon") { - log.Info("reconciling for a reference addon object named reference-addon") - } else { - log.Info("reconciling for a reference addon object not prefixed by redhat- or named reference-addon") - } - - return phase.Success() -} - -type PhaseSimulateReconciliationConfig struct { - Log logr.Logger -} - -func (c *PhaseSimulateReconciliationConfig) Option(opts ...PhaseSimulateReconciliationOption) { - for _, opt := range opts { - opt.ConfigurePhaseSimulateReconciliation(c) - } -} - -func (c *PhaseSimulateReconciliationConfig) Default() { - if c.Log.GetSink() == nil { - c.Log = logr.Discard() - } -} - -type PhaseSimulateReconciliationOption interface { - ConfigurePhaseSimulateReconciliation(*PhaseSimulateReconciliationConfig) -} diff --git a/internal/controllers/phase_simulate_reconciliation_test.go b/internal/controllers/phase_simulate_reconciliation_test.go deleted file mode 100644 index 3634bf14..00000000 --- a/internal/controllers/phase_simulate_reconciliation_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package controllers - -import ( - "context" - "testing" - - "github.com/openshift/reference-addon/internal/controllers/phase" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" -) - -func TestPhaseSimulateReconciliationInterfaces(t *testing.T) { - t.Parallel() - - require.Implements(t, new(phase.Phase), new(PhaseSimulateReconciliation)) -} - -func TestPhaseSimulateReconciliation(t *testing.T) { - t.Parallel() - - for name, tc := range map[string]struct { - Request phase.Request - }{ - "redhat prefixed object": { - Request: phase.Request{ - Object: types.NamespacedName{ - Name: "redhat-test", - }, - }, - }, - "reference-addon object": { - Request: phase.Request{ - Object: types.NamespacedName{ - Name: "reference-addon", - }, - }, - }, - "non redhat prefixed object": { - Request: phase.Request{ - Object: types.NamespacedName{ - Name: "test", - }, - }, - }, - } { - tc := tc - - t.Run(name, func(t *testing.T) { - t.Parallel() - - p := NewPhaseSimulateReconciliation() - - res := p.Execute(context.Background(), tc.Request) - require.NoError(t, res.Error()) - - assert.True(t, res.IsSuccess()) - }) - } -} diff --git a/internal/controllers/phase_uninstall.go b/internal/controllers/phase_uninstall.go index 478bd7e0..be84b40c 100644 --- a/internal/controllers/phase_uninstall.go +++ b/internal/controllers/phase_uninstall.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/go-logr/logr" + refv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" "github.com/openshift/reference-addon/internal/controllers/phase" opsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "go.uber.org/multierr" @@ -48,7 +49,14 @@ func (p *PhaseUninstall) Execute(ctx context.Context, req phase.Request) phase.R return phase.Error(fmt.Errorf("uninstalling addon: %w", err)) } - return phase.Blocking() + return phase.Blocking( + phase.WithConditions{ + newAvailableCondition( + refv1alpha1.ReferenceAddonAvailableReasonUninstalling, + "uninstallation started", + ), + }, + ) } type PhaseUninstallConfig struct { diff --git a/internal/controllers/reference_addon_controller.go b/internal/controllers/reference_addon_controller.go index bb8df6f7..a9adb91c 100644 --- a/internal/controllers/reference_addon_controller.go +++ b/internal/controllers/reference_addon_controller.go @@ -3,23 +3,25 @@ package controllers import ( "context" "fmt" - "strings" "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/labels" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - refapisv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" + refv1alpha1 "github.com/openshift/reference-addon/apis/reference/v1alpha1" "github.com/openshift/reference-addon/internal/controllers/phase" "github.com/openshift/reference-addon/internal/metrics" opsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ) func NewReferenceAddonReconciler(client client.Client, getter ParameterGetter, opts ...ReferenceAddonReconcilerOption) (*ReferenceAddonReconciler, error) { @@ -39,15 +41,15 @@ func NewReferenceAddonReconciler(client client.Client, getter ParameterGetter, o } var ( - phaseLog = cfg.Log.WithName("phase") - phaseApplyNetworkPoliciesLog = phaseLog.WithName("applyNetworkPolicies") - phaseSimulateReconciliationLog = phaseLog.WithName("simulateReconciliation") - phaseUninstallLog = phaseLog.WithName("uninstall") - uninstallerLog = phaseUninstallLog.WithName("uninstaller") + phaseLog = cfg.Log.WithName("phase") + phaseApplyNetworkPoliciesLog = phaseLog.WithName("applyNetworkPolicies") + phaseUninstallLog = phaseLog.WithName("uninstall") + uninstallerLog = phaseUninstallLog.WithName("uninstaller") ) return &ReferenceAddonReconciler{ cfg: cfg, + client: NewReferenceAddonClient(client), paramGetter: getter, orderedPhases: []phase.Phase{ NewPhaseUninstall( @@ -63,9 +65,6 @@ func NewReferenceAddonReconciler(client client.Client, getter ParameterGetter, o WithAddonNamespace(cfg.AddonNamespace), WithOperatorName(cfg.OperatorName), ), - NewPhaseSimulateReconciliation( - WithLog{Log: phaseSimulateReconciliationLog}, - ), NewPhaseSendDummyMetrics( metrics.NewResponseSamplerImpl(), WithSampleURLs{"https://httpstat.us/503", "https://httpstat.us/200"}, @@ -95,6 +94,7 @@ func NewReferenceAddonReconciler(client client.Client, getter ParameterGetter, o type ReferenceAddonReconciler struct { cfg ReferenceAddonReconcilerConfig + client ReferenceAddonClient paramGetter ParameterGetter orderedPhases []phase.Phase @@ -108,64 +108,120 @@ func (r *ReferenceAddonReconciler) Reconcile(ctx context.Context, req ctrl.Reque r.cfg.Log.Error(err, "unable to sync addon parameters") } + addon, err := r.ensureReferenceAddon(ctx) + if err != nil { + return ctrl.Result{}, fmt.Errorf("ensuring ReferenceAddon: %w", err) + } + + defer func() { + if err := r.client.UpdateStatus(ctx, addon); err != nil { + r.cfg.Log.Error(err, "updating ReferenceAddon status") + } + }() + + if !addon.HasConditionAvailable() { + meta.SetStatusCondition( + &addon.Status.Conditions, + newAvailableCondition( + refv1alpha1.ReferenceAddonAvailableReasonPending, + "starting reconciliation", + ), + ) + } + phaseReq := phase.Request{ - Object: req.NamespacedName, + Addon: *addon, Params: params, } for _, p := range r.orderedPhases { - if res := p.Execute(ctx, phaseReq); res.Error() != nil { + res := p.Execute(ctx, phaseReq) + + for _, cond := range res.Conditions() { + cond.ObservedGeneration = addon.Generation + + meta.SetStatusCondition(&addon.Status.Conditions, cond) + } + + switch res.Status() { + case phase.StatusError: return ctrl.Result{}, res.Error() - } else if !res.IsSuccess() { + case phase.StatusFailure: return ctrl.Result{Requeue: true}, nil - } else if res.IsBlocking() { + case phase.StatusBlocking: return ctrl.Result{}, nil } } + meta.SetStatusCondition(&addon.Status.Conditions, + newAvailableCondition( + refv1alpha1.ReferenceAddonAvailableReasonReady, + "all reconcile phases completed successfully", + ), + ) + return ctrl.Result{}, nil } +func (r *ReferenceAddonReconciler) ensureReferenceAddon(ctx context.Context) (*refv1alpha1.ReferenceAddon, error) { + actual, err := r.client.CreateOrUpdate(ctx, r.desiredReferenceAddon()) + if err != nil { + return nil, fmt.Errorf("creating/updating desired ReferenceAddon: %w", err) + } + + return actual, nil +} + func (r *ReferenceAddonReconciler) SetupWithManager(mgr ctrl.Manager) error { + desired := r.desiredReferenceAddon() + requestObject := types.NamespacedName{ + Name: desired.Name, + Namespace: desired.Namespace, + } + + refAddonHandler := handler.EnqueueRequestsFromMapFunc(func(_ client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: requestObject, + }, + } + }) + return ctrl.NewControllerManagedBy(mgr). - For(&refapisv1alpha1.ReferenceAddon{}). + For(&refv1alpha1.ReferenceAddon{}). + Watches( + enqueueObject(requestObject), + refAddonHandler, + ). + Owns( + &netv1.NetworkPolicy{}, + builder.WithPredicates(hasName(generateIngressPolicyName(r.cfg.OperatorName))), + ). Watches( &source.Kind{Type: &opsv1alpha1.ClusterServiceVersion{}}, - &handler.EnqueueRequestForObject{}, + refAddonHandler, builder.WithPredicates(hasNamePrefix(r.cfg.OperatorName)), ). Watches( &source.Kind{Type: &corev1.ConfigMap{}}, - &handler.EnqueueRequestForObject{}, + refAddonHandler, builder.WithPredicates(hasName(r.cfg.OperatorName)), ). Watches( &source.Kind{Type: &corev1.Secret{}}, - &handler.EnqueueRequestForObject{}, + refAddonHandler, builder.WithPredicates(hasName(r.cfg.AddonParameterSecretname)), ). - Watches( - &source.Kind{Type: &netv1.NetworkPolicy{}}, - &handler.EnqueueRequestForObject{}, - builder.WithPredicates(hasName(generateIngressPolicyName(r.cfg.OperatorName))), - ). Complete(r) } -func hasNamePrefix(pfx string) predicate.Funcs { - return predicate.NewPredicateFuncs( - func(obj client.Object) bool { - return strings.HasPrefix(obj.GetName(), pfx) - }, - ) -} - -func hasName(name string) predicate.Funcs { - return predicate.NewPredicateFuncs( - func(obj client.Object) bool { - return obj.GetName() == name +func (r *ReferenceAddonReconciler) desiredReferenceAddon() refv1alpha1.ReferenceAddon { + return refv1alpha1.ReferenceAddon{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.cfg.OperatorName, + Namespace: r.cfg.AddonNamespace, }, - ) + } } type ReferenceAddonReconcilerConfig struct { @@ -192,3 +248,46 @@ func (c *ReferenceAddonReconcilerConfig) Default() { type ReferenceAddonReconcilerOption interface { ConfigureReferenceAddonReconciler(*ReferenceAddonReconcilerConfig) } + +type ReferenceAddonClient interface { + CreateOrUpdate(ctx context.Context, addon refv1alpha1.ReferenceAddon) (*refv1alpha1.ReferenceAddon, error) + UpdateStatus(ctx context.Context, addon *refv1alpha1.ReferenceAddon) error +} + +func NewReferenceAddonClient(client client.Client) *ReferenceAddonClientImpl { + return &ReferenceAddonClientImpl{ + client: client, + } +} + +type ReferenceAddonClientImpl struct { + client client.Client +} + +func (c *ReferenceAddonClientImpl) CreateOrUpdate(ctx context.Context, addon refv1alpha1.ReferenceAddon) (*refv1alpha1.ReferenceAddon, error) { + actualAddon := &refv1alpha1.ReferenceAddon{ + ObjectMeta: metav1.ObjectMeta{ + Name: addon.Name, + Namespace: addon.Namespace, + }, + } + + if _, err := ctrl.CreateOrUpdate(ctx, c.client, actualAddon, func() error { + actualAddon.Labels = labels.Merge(actualAddon.Labels, addon.Labels) + actualAddon.Spec = addon.Spec + + return nil + }); err != nil { + return nil, fmt.Errorf("creating/updating ReferenceAddon: %w", err) + } + + return actualAddon, nil +} + +func (c *ReferenceAddonClientImpl) UpdateStatus(ctx context.Context, addon *refv1alpha1.ReferenceAddon) error { + if err := c.client.Status().Update(ctx, addon); err != nil { + return fmt.Errorf("updating ReferenceAddon status: %w", err) + } + + return nil +}