Skip to content

Commit 458561f

Browse files
authored
Add component revision to status (#362)
* add new status field: status.revision * expose revision to kustomize generator * update website * further improvements
1 parent 3f03455 commit 458561f

15 files changed

Lines changed: 184 additions & 53 deletions

File tree

clm/internal/manifests/component.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (c *Component) GetStatus() *component.Status {
3434
// LastAppliedAt
3535
// ProcessingDigest
3636
// ProcessingSince
37+
Revision: c.release.Revision,
3738
// Conditions
3839
State: c.release.State,
3940
Inventory: c.release.Inventory,

clm/internal/manifests/generate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ func Generate(manifestSources []string, valuesSources []string, reconcilerName s
9696
WithComponent(releaseComponent).
9797
WithComponentName(releaseComponent.GetName()).
9898
WithComponentNamespace(releaseComponent.GetNamespace()).
99-
WithComponentDigest("")
99+
WithComponentDigest("").
100+
WithComponentRevision(releaseComponent.GetStatus().Revision)
100101
objects, err := generator.Generate(generateCtx, release.GetNamespace(), release.GetName(), types.UnstructurableMap(allValues))
101102
if err != nil {
102103
return nil, err

internal/helm/chart_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var _ = Describe("testing: chart.go", func() {
6363
Service: "Helm",
6464
IsInstall: true,
6565
IsUpgrade: false,
66+
Revision: 1,
6667
},
6768
Values: values,
6869
})

internal/helm/resources.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and component-operator-runtime contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package helm
7+
8+
import (
9+
"fmt"
10+
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
const (
15+
annotationKeyResourcePolicy = "helm.sh/resource-policy"
16+
)
17+
18+
// parse helm resource properties from object, return nil if none is set
19+
func ParseResourceMetadata(object client.Object) (*ResourceMetadata, error) {
20+
metadata := &ResourceMetadata{}
21+
annotations := object.GetAnnotations()
22+
23+
if value, ok := annotations[annotationKeyResourcePolicy]; ok {
24+
metadata.Policy = value
25+
switch metadata.Policy {
26+
case ResourcePolicyKeep:
27+
default:
28+
return nil, fmt.Errorf("invalid resource policy: %s", metadata.Policy)
29+
}
30+
}
31+
32+
return metadata, nil
33+
}

internal/helm/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type Release struct {
7575
Service string `json:"service,omitempty"`
7676
IsInstall bool `json:"isInstall,omitempty"`
7777
IsUpgrade bool `json:"isUpgrade,omitempty"`
78+
Revision int64 `json:"revision"`
7879
}
7980

8081
// +kubebuilder:object:generate=true
@@ -111,6 +112,14 @@ func (apiVersions ApiVersions) Has(version string) bool {
111112
return false
112113
}
113114

115+
type ResourceMetadata struct {
116+
Policy string
117+
}
118+
119+
const (
120+
ResourcePolicyKeep = "keep"
121+
)
122+
114123
type HookMetadata struct {
115124
Types []string
116125
Weight int

internal/kustomize/kustomization.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@ type KustomizationOptions struct {
6060
}
6161

6262
type RenderContext struct {
63-
LocalClient client.Client
64-
Client client.Client
65-
DiscoveryClient discovery.DiscoveryInterface
66-
Component component.Component
67-
ComponentDigest string
68-
Namespace string
69-
Name string
70-
Parameters map[string]any
63+
LocalClient client.Client
64+
Client client.Client
65+
DiscoveryClient discovery.DiscoveryInterface
66+
Component component.Component
67+
ComponentDigest string
68+
ComponentRevision int64
69+
Namespace string
70+
Name string
71+
Parameters map[string]any
7172
}
7273

7374
type Kustomization struct {
@@ -178,7 +179,7 @@ func parseKustomization(fsys fs.FS, kustomizationPath string, options Kustomizat
178179
Funcs(templatex.FuncMapForTemplate(nil)).
179180
Funcs(templatex.FuncMapForLocalClient(nil)).
180181
Funcs(templatex.FuncMapForClient(nil)).
181-
Funcs(funcMapForContext(nil, nil, nil, nil, "", "", ""))
182+
Funcs(funcMapForContext(nil, nil, nil, nil, "", 0, "", ""))
182183
} else {
183184
t = t.New(name)
184185
}
@@ -290,7 +291,7 @@ func (k *Kustomization) Render(context RenderContext, fsys kustfsys.FileSystem)
290291
Funcs(templatex.FuncMapForTemplate(t0)).
291292
Funcs(templatex.FuncMapForLocalClient(context.LocalClient)).
292293
Funcs(templatex.FuncMapForClient(context.Client)).
293-
Funcs(funcMapForContext(k.files, serverVersion, serverGroupsWithResources, context.Component, context.ComponentDigest, context.Namespace, context.Name))
294+
Funcs(funcMapForContext(k.files, serverVersion, serverGroupsWithResources, context.Component, context.ComponentDigest, context.ComponentRevision, context.Namespace, context.Name))
294295
}
295296
var buf bytes.Buffer
296297
// TODO: templates (accidentally or intentionally) could modify data, or even some of the objects supplied through builtin functions;
@@ -330,7 +331,7 @@ func (k *Kustomization) Render(context RenderContext, fsys kustfsys.FileSystem)
330331
return nil
331332
}
332333

333-
func funcMapForContext(files map[string][]byte, serverInfo *version.Info, serverGroupsWithResources []*metav1.APIResourceList, component component.Component, componentDigest string, namespace string, name string) template.FuncMap {
334+
func funcMapForContext(files map[string][]byte, serverInfo *version.Info, serverGroupsWithResources []*metav1.APIResourceList, component component.Component, componentDigest string, componentRevision int64, namespace string, name string) template.FuncMap {
334335
return template.FuncMap{
335336
// TODO: maybe it would it be better to convert component to unstructured;
336337
// then calling methods would no longer be possible, and attributes would be in lowercase
@@ -339,6 +340,7 @@ func funcMapForContext(files map[string][]byte, serverInfo *version.Info, server
339340
"readFile": makeFuncReadFile(files),
340341
"component": makeFuncData(component),
341342
"componentDigest": func() string { return componentDigest },
343+
"componentRevision": func() int64 { return componentRevision },
342344
"namespace": func() string { return namespace },
343345
"name": func() string { return name },
344346
"kubernetesVersion": func() *version.Info { return serverInfo },

pkg/component/context.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type (
2020
componentNameContextKeyType struct{}
2121
componentNamespaceContextKeyType struct{}
2222
componentDigestContextKeyType struct{}
23+
componentRevisionContextKeyType struct{}
2324
)
2425

2526
var (
@@ -30,6 +31,7 @@ var (
3031
componentNameContextKey = componentNameContextKeyType{}
3132
componentNamespaceContextKey = componentNamespaceContextKeyType{}
3233
componentDigestContextKey = componentDigestContextKeyType{}
34+
componentRevisionContextKey = componentRevisionContextKeyType{}
3335
)
3436

3537
type Context interface {
@@ -41,6 +43,7 @@ type Context interface {
4143
WithComponentName(componentName string) Context
4244
WithComponentNamespace(componentNamespace string) Context
4345
WithComponentDigest(componentDigest string) Context
46+
WithComponentRevision(componentRevision int64) Context
4447
}
4548

4649
func NewContext(ctx context.Context) Context {
@@ -79,6 +82,10 @@ func (c *contextImpl) WithComponentDigest(componentDigest string) Context {
7982
return &contextImpl{Context: context.WithValue(c, componentDigestContextKey, componentDigest)}
8083
}
8184

85+
func (c *contextImpl) WithComponentRevision(componentRevision int64) Context {
86+
return &contextImpl{Context: context.WithValue(c, componentRevisionContextKey, componentRevision)}
87+
}
88+
8289
func ReconcilerNameFromContext(ctx context.Context) (string, error) {
8390
if reconcilerName, ok := ctx.Value(reconcilerNameContextKey).(string); ok {
8491
return reconcilerName, nil
@@ -128,3 +135,10 @@ func ComponentDigestFromContext(ctx context.Context) (string, error) {
128135
}
129136
return "", fmt.Errorf("component digest not found in context")
130137
}
138+
139+
func ComponentRevisionFromContext(ctx context.Context) (int64, error) {
140+
if componentRevision, ok := ctx.Value(componentRevisionContextKey).(int64); ok {
141+
return componentRevision, nil
142+
}
143+
return 0, fmt.Errorf("component revision not found in context")
144+
}

pkg/component/reconciler.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,17 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
524524
return ctrl.Result{RequeueAfter: time.Millisecond}, nil
525525
}
526526

527+
// TODO: this is temporarily needed until the revision is adopted by all consumers and rolled out completely
528+
// otherwise, existing components would have revision == 1 which might lead to problems with helm generator
529+
if status.Revision == 0 && status.LastAppliedAt != nil {
530+
status.Revision = 1
531+
}
532+
533+
if status.ProcessingDigest != status.LastProcessingDigest {
534+
status.Revision += 1
535+
status.LastProcessingDigest = status.ProcessingDigest
536+
}
537+
527538
log.V(2).Info("reconciling dependent resources")
528539
for hookOrder, hook := range r.preReconcileHooks {
529540
if err := hook(hookCtx, r.hookClient, component); err != nil {

pkg/component/target.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ func (t *reconcileTarget[T]) Apply(ctx context.Context, component T, componentDi
5252
ownerId := t.reconcilerId + "/" + component.GetNamespace() + "/" + component.GetName()
5353
status := component.GetStatus()
5454

55+
// TODO: componentDigest should always equal status.ProcessingDigest;
56+
// so it should be ok to remove that parameter from the method
57+
if componentDigest != status.ProcessingDigest {
58+
panic("this cannot happen")
59+
}
60+
5561
// TODO: enhance ctx with local client
5662
generateCtx := NewContext(ctx).
5763
WithReconcilerName(t.reconcilerName).
@@ -60,7 +66,8 @@ func (t *reconcileTarget[T]) Apply(ctx context.Context, component T, componentDi
6066
WithComponent(component).
6167
WithComponentName(component.GetName()).
6268
WithComponentNamespace(component.GetNamespace()).
63-
WithComponentDigest(componentDigest)
69+
WithComponentDigest(componentDigest).
70+
WithComponentRevision(status.Revision)
6471
objects, err := t.resourceGenerator.Generate(generateCtx, namespace, name, component.GetSpec())
6572
if err != nil {
6673
return false, legacyerrors.Wrap(err, "error rendering manifests")

pkg/component/types.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,15 @@ var _ ReapplyConfiguration = &ReapplySpec{}
246246

247247
// Component Status. Components must include this into their status.
248248
type Status struct {
249-
ObservedGeneration int64 `json:"observedGeneration"`
250-
AppliedGeneration int64 `json:"appliedGeneration,omitempty"`
251-
LastObservedAt *metav1.Time `json:"lastObservedAt,omitempty"`
252-
LastAppliedAt *metav1.Time `json:"lastAppliedAt,omitempty"`
253-
ProcessingDigest string `json:"processingDigest,omitempty"`
254-
ProcessingSince *metav1.Time `json:"processingSince,omitempty"`
255-
Conditions []Condition `json:"conditions,omitempty"`
249+
ObservedGeneration int64 `json:"observedGeneration"`
250+
AppliedGeneration int64 `json:"appliedGeneration,omitempty"`
251+
LastObservedAt *metav1.Time `json:"lastObservedAt,omitempty"`
252+
LastAppliedAt *metav1.Time `json:"lastAppliedAt,omitempty"`
253+
ProcessingDigest string `json:"processingDigest,omitempty"`
254+
ProcessingSince *metav1.Time `json:"processingSince,omitempty"`
255+
LastProcessingDigest string `json:"lastProcessingDigest,omitempty"`
256+
Revision int64 `json:"revision,omitempty"`
257+
Conditions []Condition `json:"conditions,omitempty"`
256258
// +kubebuilder:validation:Enum=Ready;Pending;Processing;DeletionPending;Deleting;Error
257259
State State `json:"state,omitempty"`
258260
Inventory []*reconciler.InventoryItem `json:"inventory,omitempty"`

0 commit comments

Comments
 (0)