Skip to content

Commit 58e3dd9

Browse files
authored
Merge branch 'main' into post-renderer
2 parents 2740556 + 8604405 commit 58e3dd9

13 files changed

Lines changed: 166 additions & 83 deletions

File tree

.github/workflows/stale-issue-pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
steps:
2121
- name: Mark stale issues and PRs
22-
uses: actions/stale@v10.1.1 # Use the latest version of the Stale Action
22+
uses: actions/stale@v10.2.0 # Use the latest version of the Stale Action
2323
with:
2424
# Label to apply to stale issues
2525
stale-issue-label: 'stale'

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ require (
2727
sigs.k8s.io/controller-tools v0.20.1
2828
sigs.k8s.io/kustomize/api v0.21.1
2929
sigs.k8s.io/kustomize/kyaml v0.21.1
30-
sigs.k8s.io/structured-merge-diff/v6 v6.3.2
30+
sigs.k8s.io/structured-merge-diff/v6 v6.4.0
3131
sigs.k8s.io/yaml v1.6.0
3232
)
3333

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,5 +378,7 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
378378
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
379379
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
380380
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
381+
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo=
382+
sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
381383
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
382384
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

internal/helm/chart.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ func ParseChart(fsys fs.FS, chartPath string, parent *Chart) (*Chart, error) {
8383
if err := kyaml.Unmarshal(chartRaw, chart.metadata); err != nil {
8484
return nil, err
8585
}
86+
if chart.metadata.Dependencies == nil {
87+
requirementsRaw, err := fs.ReadFile(fsys, filepath.Clean(chartPath+"/requirements.yaml"))
88+
if err == nil {
89+
requirements := struct {
90+
Dependencies []ChartDependency `json:"dependencies,omitempty"`
91+
}{}
92+
if err := kyaml.Unmarshal(requirementsRaw, &requirements); err != nil {
93+
return nil, err
94+
}
95+
chart.metadata.Dependencies = requirements.Dependencies
96+
} else if !errors.Is(err, fs.ErrNotExist) {
97+
return nil, err
98+
}
99+
}
86100
if chart.metadata.Type == "" {
87101
chart.metadata.Type = ChartTypeApplication
88102
}

internal/helm/util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"strings"
1111
)
1212

13+
// TODO: consolidate all the util files into an internal reuse package
14+
1315
/*
1416
func getString(data map[string]any, key string) (string, bool, bool) {
1517
if v, ok := data[key]; ok {

internal/kustomize/kustomization.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func parseKustomization(fsys fs.FS, kustomizationPath string, options Kustomizat
196196

197197
for _, path := range options.IncludedFiles {
198198
if filepath.IsAbs(path) {
199-
return nil, fmt.Errorf("include path (%s) must be absolute", path)
199+
return nil, fmt.Errorf("include path (%s) must not be absolute", path)
200200
}
201201
absolutePath := filepath.Clean(filepath.Join(kustomizationPath, path))
202202
if isSubdirectory(absolutePath, kustomizationPath) {
@@ -392,20 +392,22 @@ func makeFuncData(data any) any {
392392
func generateKustomization(fsys kustfsys.FileSystem, kustomizationPath string) ([]byte, error) {
393393
var resources []string
394394

395-
f := func(path string, info fs.FileInfo, err error) error {
396-
if err != nil {
397-
return err
398-
}
399-
// TODO: IsDir() is false if it is a symlink; is that wanted to be this way?
400-
if !info.IsDir() && !strings.HasPrefix(filepath.Base(path), ".") && (strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) {
401-
resources = append(resources, path)
395+
if fsys.IsDir(kustomizationPath) {
396+
f := func(path string, info fs.FileInfo, err error) error {
397+
if err != nil {
398+
return err
399+
}
400+
// TODO: IsDir() is false if it is a symlink; is that wanted to be this way?
401+
if !info.IsDir() && !strings.HasPrefix(filepath.Base(path), ".") && (strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) {
402+
resources = append(resources, path)
403+
}
404+
return nil
402405
}
403-
return nil
404-
}
405406

406-
// TODO: does this work correctly with symlinks?
407-
if err := fsys.Walk(kustomizationPath, f); err != nil {
408-
return nil, err
407+
// TODO: does this work correctly with symlinks?
408+
if err := fsys.Walk(kustomizationPath, f); err != nil {
409+
return nil, err
410+
}
409411
}
410412

411413
kustomization := kusttypes.Kustomization{

internal/util/finalizer.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator-runtime contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package util
7+
8+
import (
9+
"context"
10+
"encoding/json"
11+
12+
apitypes "k8s.io/apimachinery/pkg/types"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
)
15+
16+
// This is a workaround. Ideally Kubernetes would have a finalizers subresource, and controller-runtime
17+
// would offer the according subresource client.
18+
func UpdateFinalizers(ctx context.Context, clnt client.Client, obj client.Object, fieldOwner string) error {
19+
// note: it's crucial to use string keyed maps to construct the patch; using metav1.ObjectMeta would lead to
20+
// finalizers to be removed entirely from the patch when empty (because of omitempty usage there);
21+
// but we need to preserve empty or null finalizers in the patch, in order to make JSON merge patch apply it correctly
22+
finalizerPatch := map[string]any{
23+
"metadata": map[string]any{
24+
"resourceVersion": obj.GetResourceVersion(),
25+
"finalizers": obj.GetFinalizers(),
26+
},
27+
}
28+
// note: this must() is ok because marshalling finalizers should always work
29+
return clnt.Patch(ctx, obj, client.RawPatch(apitypes.MergePatchType, must(json.Marshal(finalizerPatch))), client.FieldOwner(fieldOwner))
30+
}

internal/util/util.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator-runtime contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package util
7+
8+
// TODO: consolidate all the util files into an internal reuse package
9+
10+
func must[T any](x T, err error) T {
11+
if err != nil {
12+
panic(err)
13+
}
14+
return x
15+
}

pkg/component/reconciler.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"github.com/sap/component-operator-runtime/internal/clientfactory"
4646
"github.com/sap/component-operator-runtime/internal/events"
4747
"github.com/sap/component-operator-runtime/internal/metrics"
48+
"github.com/sap/component-operator-runtime/internal/util"
4849
"github.com/sap/component-operator-runtime/pkg/cluster"
4950
"github.com/sap/component-operator-runtime/pkg/manifests"
5051
"github.com/sap/component-operator-runtime/pkg/reconciler"
@@ -514,7 +515,7 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
514515
// create/update case
515516
// TODO: optionally (to be completely consistent) set finalizer through a mutating webhook
516517
if added := controllerutil.AddFinalizer(component, *r.options.Finalizer); added {
517-
if err := r.client.Update(ctx, component, client.FieldOwner(*r.options.FieldOwner)); err != nil {
518+
if err := util.UpdateFinalizers(ctx, r.client, component, *r.options.FieldOwner); err != nil {
518519
return ctrl.Result{}, legacyerrors.Wrap(err, "error adding finalizer")
519520
}
520521
// trigger another round trip
@@ -610,7 +611,7 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
610611
// all dependent resources are already gone, so that's it
611612
log.V(1).Info("all dependent resources are successfully deleted; removing finalizer")
612613
if removed := controllerutil.RemoveFinalizer(component, *r.options.Finalizer); removed {
613-
if err := r.client.Update(ctx, component, client.FieldOwner(*r.options.FieldOwner)); err != nil {
614+
if err := util.UpdateFinalizers(ctx, r.client, component, *r.options.FieldOwner); err != nil {
614615
return ctrl.Result{}, legacyerrors.Wrap(err, "error removing finalizer")
615616
}
616617
}

pkg/reconciler/reconciler.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3535
"sigs.k8s.io/controller-runtime/pkg/log"
3636

37+
"github.com/sap/component-operator-runtime/internal/util"
3738
"github.com/sap/component-operator-runtime/pkg/cluster"
3839
"github.com/sap/component-operator-runtime/pkg/status"
3940
"github.com/sap/component-operator-runtime/pkg/types"
@@ -1318,7 +1319,7 @@ func (r *Reconciler) deleteObject(ctx context.Context, key types.ObjectKey, exis
13181319
}
13191320
if ok := controllerutil.RemoveFinalizer(crd, r.finalizer); ok {
13201321
// note: 409 error is very likely here (because of concurrent updates happening through the api server); this is why we retry once
1321-
if err := r.client.Update(ctx, crd, client.FieldOwner(r.fieldOwner)); err != nil {
1322+
if err := util.UpdateFinalizers(ctx, r.client, crd, r.fieldOwner); err != nil {
13221323
if i == 1 && apierrors.IsConflict(err) {
13231324
log.V(1).Info("error while updating CustomResourcedefinition (409 conflict); doing one retry", "error", err.Error())
13241325
continue
@@ -1343,7 +1344,7 @@ func (r *Reconciler) deleteObject(ctx context.Context, key types.ObjectKey, exis
13431344
}
13441345
if ok := controllerutil.RemoveFinalizer(apiService, r.finalizer); ok {
13451346
// note: 409 error is very likely here (because of concurrent updates happening through the api server); this is why we retry once
1346-
if err := r.client.Update(ctx, apiService, client.FieldOwner(r.fieldOwner)); err != nil {
1347+
if err := util.UpdateFinalizers(ctx, r.client, apiService, r.fieldOwner); err != nil {
13471348
if i == 1 && apierrors.IsConflict(err) {
13481349
log.V(1).Info("error while updating APIService (409 conflict); doing one retry", "error", err.Error())
13491350
continue
@@ -1365,13 +1366,15 @@ func (r *Reconciler) orphanObject(ctx context.Context, existingObject *unstructu
13651366
}
13661367

13671368
if existingObject.GetLabels()[r.labelKeyOwnerId] != hashedOwnerId {
1368-
return fmt.Errorf("owner conflict; object %s has no or different owner", types.ObjectKeyToString(existingObject))
1369+
// the object has a different owner; so we do not raise an owner id conflict error here
1370+
return nil
13691371
}
13701372

1373+
// do cleanup on orphaned object; note: this happens only if we own it; otherwise we already returned above
13711374
if isCrd(existingObject) || isApiService(existingObject) {
13721375
object := existingObject.DeepCopy()
13731376
if controllerutil.RemoveFinalizer(object, r.finalizer) {
1374-
if err := r.client.Update(ctx, object, client.FieldOwner(r.fieldOwner)); err != nil {
1377+
if err := util.UpdateFinalizers(ctx, r.client, object, r.fieldOwner); err != nil {
13751378
return err
13761379
}
13771380
}
@@ -1528,9 +1531,11 @@ func (r *Reconciler) isTypeUsed(ctx context.Context, gk schema.GroupKind, hashed
15281531

15291532
func (r *Reconciler) isCrdUsed(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, hashedOwnerId string, onlyForeign bool) (bool, error) {
15301533
gvk := schema.GroupVersionKind{
1531-
Group: crd.Spec.Group,
1532-
Version: crd.Spec.Versions[0].Name,
1533-
Kind: crd.Spec.Names.Kind,
1534+
Group: crd.Spec.Group,
1535+
Version: slices.Select(crd.Spec.Versions, func(v apiextensionsv1.CustomResourceDefinitionVersion) bool {
1536+
return v.Served
1537+
})[0].Name,
1538+
Kind: crd.Spec.Names.Kind,
15341539
}
15351540
list := &unstructured.UnstructuredList{}
15361541
list.SetGroupVersionKind(gvk)

0 commit comments

Comments
 (0)