Skip to content

Commit 1e032f5

Browse files
committed
Update report after instance reconciliation
Signed-off-by: Stefan Prodan <[email protected]>
1 parent e1f1c46 commit 1e032f5

File tree

4 files changed

+74
-15
lines changed

4 files changed

+74
-15
lines changed

docs/api/v1/fluxreport.md

+9
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,15 @@ spec:
244244
status: 'Applied revision: refs/heads/main@sha1:a90cd1ac35de01c175f7199315d3f4cd60195911'
245245
```
246246

247+
## Generating a FluxReport
248+
249+
The FluxReport is automatically generated by the operator for the following conditions:
250+
251+
- At startup, when the operator is installed or upgraded.
252+
- When the [FluxInstance](fluxinstance.md) is created or updated.
253+
- When the `reconcile.fluxcd.io/requestedAt` annotation is set on the FluxReport resource.
254+
- At regular intervals, controlled by the `fluxcd.controlplane.io/reconcileEvery` annotation.
255+
247256
### Reconciliation configuration
248257

249258
The reconciliation behaviour can be configured using the following annotations:

internal/controller/fluxinstance_controller.go

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
3333
"github.com/controlplaneio-fluxcd/flux-operator/internal/builder"
3434
"github.com/controlplaneio-fluxcd/flux-operator/internal/inventory"
35+
"github.com/controlplaneio-fluxcd/flux-operator/internal/reporter"
3536
)
3637

3738
// FluxInstanceReconciler reconciles a FluxInstance object
@@ -68,6 +69,12 @@ func (r *FluxInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request
6869
log.Error(err, "failed to update status")
6970
retErr = kerrors.NewAggregate([]error{retErr, err})
7071
}
72+
73+
if err := reporter.RequestReportUpdate(ctx,
74+
r.Client, fluxcdv1.DefaultInstanceName,
75+
r.StatusManager, obj.Namespace); err != nil {
76+
log.Error(err, "failed to request report update")
77+
}
7178
}()
7279

7380
// Uninstall if the object is under deletion.

internal/controller/fluxreport_controller_test.go

+18-15
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,24 @@ func TestFluxReportReconciler_Reconcile(t *testing.T) {
2929
ns, err := testEnv.CreateNamespace(ctx, "test")
3030
g.Expect(err).ToNot(HaveOccurred())
3131

32+
// Initialize the report.
33+
report := &fluxcdv1.FluxReport{
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: fluxcdv1.DefaultInstanceName,
36+
Namespace: ns.Name,
37+
},
38+
}
39+
err = reportRec.initReport(ctx, report.GetName(), report.GetNamespace())
40+
g.Expect(err).ToNot(HaveOccurred())
41+
42+
// Create the Flux instance.
3243
instance := &fluxcdv1.FluxInstance{
3344
ObjectMeta: metav1.ObjectMeta{
3445
Name: ns.Name,
3546
Namespace: ns.Name,
3647
},
3748
Spec: getDefaultFluxSpec(),
3849
}
39-
4050
err = testEnv.Create(ctx, instance)
4151
g.Expect(err).ToNot(HaveOccurred())
4252

@@ -59,17 +69,6 @@ func TestFluxReportReconciler_Reconcile(t *testing.T) {
5969
g.Expect(err).ToNot(HaveOccurred())
6070
checkInstanceReadiness(g, instance)
6171

62-
report := &fluxcdv1.FluxReport{
63-
ObjectMeta: metav1.ObjectMeta{
64-
Name: fluxcdv1.DefaultInstanceName,
65-
Namespace: ns.Name,
66-
},
67-
}
68-
69-
// Initialize the report.
70-
err = reportRec.initReport(ctx, report.GetName(), report.GetNamespace())
71-
g.Expect(err).ToNot(HaveOccurred())
72-
7372
// Compute instance report.
7473
r, err = reportRec.Reconcile(ctx, reconcile.Request{
7574
NamespacedName: client.ObjectKeyFromObject(report),
@@ -81,6 +80,9 @@ func TestFluxReportReconciler_Reconcile(t *testing.T) {
8180
g.Expect(err).ToNot(HaveOccurred())
8281
logObject(t, report)
8382

83+
// Check annotation set by the instance reconciler.
84+
g.Expect(report.GetAnnotations()).To(HaveKey(meta.ReconcileRequestAnnotation))
85+
8486
// Check reported components.
8587
g.Expect(report.Spec.ComponentsStatus).To(HaveLen(len(instance.Status.Components)))
8688
g.Expect(report.Spec.ComponentsStatus[0].Name).To(Equal("helm-controller"))
@@ -125,10 +127,11 @@ func TestFluxReportReconciler_Reconcile(t *testing.T) {
125127
g.Expect(err).ToNot(HaveOccurred())
126128

127129
// Read the report and verify distribution.
128-
err = testClient.Get(ctx, client.ObjectKeyFromObject(report), report)
130+
emptyReport := &fluxcdv1.FluxReport{}
131+
err = testClient.Get(ctx, client.ObjectKeyFromObject(report), emptyReport)
129132
g.Expect(err).ToNot(HaveOccurred())
130-
g.Expect(report.Spec.Distribution.Status).To(Equal("Not Installed"))
131-
g.Expect(report.Spec.Distribution.Entitlement).To(Equal("Issued by " + entitlement.DefaultVendor))
133+
g.Expect(emptyReport.Spec.Distribution.Status).To(Equal("Not Installed"))
134+
g.Expect(emptyReport.Spec.Distribution.Entitlement).To(Equal("Issued by " + entitlement.DefaultVendor))
132135
}
133136

134137
func getFluxReportReconciler() *FluxReportReconciler {

internal/reporter/reporter.go

+40
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ package reporter
66
import (
77
"context"
88
"fmt"
9+
"strconv"
910

11+
"github.com/fluxcd/pkg/apis/meta"
12+
"k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
1015
"sigs.k8s.io/controller-runtime/pkg/client"
1116

1217
fluxcdv1 "github.com/controlplaneio-fluxcd/flux-operator/api/v1"
@@ -65,3 +70,38 @@ func (r *FluxStatusReporter) Compute(ctx context.Context) (fluxcdv1.FluxReportSp
6570

6671
return report, nil
6772
}
73+
74+
// RequestReportUpdate annotates the FluxReport object to trigger a reconciliation.
75+
func RequestReportUpdate(ctx context.Context, kubeClient client.Client, instance, manager, namespace string) error {
76+
report := &metav1.PartialObjectMetadata{}
77+
report.SetGroupVersionKind(schema.GroupVersionKind{
78+
Group: fluxcdv1.GroupVersion.Group,
79+
Kind: fluxcdv1.FluxReportKind,
80+
Version: fluxcdv1.GroupVersion.Version,
81+
})
82+
83+
objectKey := client.ObjectKey{
84+
Namespace: namespace,
85+
Name: instance,
86+
}
87+
88+
if err := kubeClient.Get(ctx, objectKey, report); err != nil {
89+
if errors.IsNotFound(err) {
90+
return nil
91+
}
92+
return fmt.Errorf("failed to read %s '%s' error: %w", report.Kind, instance, err)
93+
}
94+
95+
patch := client.MergeFrom(report.DeepCopy())
96+
annotations := report.GetAnnotations()
97+
if annotations == nil {
98+
annotations = make(map[string]string)
99+
}
100+
annotations[meta.ReconcileRequestAnnotation] = strconv.FormatInt(metav1.Now().Unix(), 10)
101+
report.SetAnnotations(annotations)
102+
103+
if err := kubeClient.Patch(ctx, report, patch); err != nil {
104+
return fmt.Errorf("failed to annotate %s '%s' error: %w", report.Kind, instance, err)
105+
}
106+
return nil
107+
}

0 commit comments

Comments
 (0)