• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

operator-framework / operator-sdk / 28266750132

26 Jun 2026 09:37PM UTC coverage: 31.37%. First build
28266750132

Pull #7110

github

acornett21
migrating to helm v4

Signed-off-by: Adam D. Cornett <adc@redhat.com>
Pull Request #7110: migrating to helm v4

17 of 85 new or added lines in 9 files covered. (20.0%)

4384 of 13975 relevant lines covered (31.37%)

13.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/internal/helm/controller/controller.go
1
// Copyright 2018 The Operator-SDK Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package controller
16

17
import (
18
        "fmt"
19
        "strings"
20
        "sync"
21
        "time"
22

23
        rpb "helm.sh/helm/v4/pkg/release/v1"
24
        releaseutil "helm.sh/helm/v4/pkg/release/v1/util"
25
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27
        "k8s.io/apimachinery/pkg/runtime"
28
        "k8s.io/apimachinery/pkg/runtime/schema"
29
        "sigs.k8s.io/controller-runtime/pkg/client"
30
        "sigs.k8s.io/controller-runtime/pkg/controller"
31
        crthandler "sigs.k8s.io/controller-runtime/pkg/handler"
32
        logf "sigs.k8s.io/controller-runtime/pkg/log"
33
        "sigs.k8s.io/controller-runtime/pkg/manager"
34
        "sigs.k8s.io/controller-runtime/pkg/source"
35
        "sigs.k8s.io/yaml"
36

37
        libhandler "github.com/operator-framework/operator-lib/handler"
38
        "github.com/operator-framework/operator-lib/predicate"
39
        "github.com/operator-framework/operator-sdk/internal/helm/release"
40
        "github.com/operator-framework/operator-sdk/internal/util/k8sutil"
41
)
42

43
var log = logf.Log.WithName("helm.controller")
44

45
// WatchOptions contains the necessary values to create a new controller that
46
// manages helm releases in a particular namespace based on a GVK watch.
47
type WatchOptions struct {
48
        GVK                     schema.GroupVersionKind
49
        ManagerFactory          release.ManagerFactory
50
        ReconcilePeriod         time.Duration
51
        WatchDependentResources bool
52
        OverrideValues          map[string]string
53
        SuppressOverrideValues  bool
54
        MaxConcurrentReconciles int
55
        Selector                metav1.LabelSelector
56
        DryRunOption            string
57
}
58

59
// Add creates a new helm operator controller and adds it to the manager
60
func Add(mgr manager.Manager, options WatchOptions) error {
×
61
        controllerName := fmt.Sprintf("%v-controller", strings.ToLower(options.GVK.Kind))
×
62

×
63
        r := &HelmOperatorReconciler{
×
64
                Client:                 mgr.GetClient(),
×
NEW
65
                EventRecorder:          mgr.GetEventRecorder(controllerName),
×
66
                GVK:                    options.GVK,
×
67
                ManagerFactory:         options.ManagerFactory,
×
68
                ReconcilePeriod:        options.ReconcilePeriod,
×
69
                OverrideValues:         options.OverrideValues,
×
70
                SuppressOverrideValues: options.SuppressOverrideValues,
×
71
                DryRunOption:           options.DryRunOption,
×
72
        }
×
73

×
74
        c, err := controller.New(controllerName, mgr, controller.Options{
×
75
                Reconciler:              r,
×
76
                MaxConcurrentReconciles: options.MaxConcurrentReconciles,
×
77
        })
×
78
        if err != nil {
×
79
                return err
×
80
        }
×
81

82
        o := &unstructured.Unstructured{}
×
83
        o.SetGroupVersionKind(options.GVK)
×
84

×
85
        if err := c.Watch(source.Kind(mgr.GetCache(), client.Object(o), &libhandler.InstrumentedEnqueueRequestForObject[client.Object]{})); err != nil {
×
86
                return err
×
87
        }
×
88

89
        if options.WatchDependentResources {
×
90
                watchDependentResources(mgr, r, c)
×
91
        }
×
92

93
        log.Info("Watching resource", "apiVersion", options.GVK.GroupVersion(), "kind",
×
94
                options.GVK.Kind, "reconcilePeriod", options.ReconcilePeriod.String())
×
95
        return nil
×
96
}
97

98
// watchDependentResources adds a release hook function to the HelmOperatorReconciler
99
// that adds watches for resources in released Helm charts.
100
func watchDependentResources(mgr manager.Manager, r *HelmOperatorReconciler, c controller.Controller) {
×
101
        var m sync.RWMutex
×
102
        watches := map[schema.GroupVersionKind]struct{}{}
×
103
        releaseHook := func(release *rpb.Release) error {
×
104
                owner := &unstructured.Unstructured{}
×
105
                owner.SetGroupVersionKind(r.GVK)
×
106
                owner.SetNamespace(release.Namespace)
×
107
                resources := releaseutil.SplitManifests(release.Manifest)
×
108
                for _, resource := range resources {
×
109
                        var u unstructured.Unstructured
×
110
                        if err := yaml.Unmarshal([]byte(resource), &u); err != nil {
×
111
                                return err
×
112
                        }
×
113

114
                        gvk := u.GroupVersionKind()
×
115
                        if gvk.Empty() {
×
116
                                continue
×
117
                        }
118

119
                        var setWatchOnResource = func(dependent runtime.Object) error {
×
120
                                unstructuredObj := dependent.(*unstructured.Unstructured)
×
121
                                gvkDependent := unstructuredObj.GroupVersionKind()
×
122
                                if gvkDependent.Empty() {
×
123
                                        return nil
×
124
                                }
×
125

126
                                m.RLock()
×
127
                                _, ok := watches[gvkDependent]
×
128
                                m.RUnlock()
×
129
                                if ok {
×
130
                                        return nil
×
131
                                }
×
132

133
                                restMapper := mgr.GetRESTMapper()
×
134
                                useOwnerRef, err := k8sutil.SupportsOwnerReference(restMapper, owner, dependent, "")
×
135
                                if err != nil {
×
136
                                        return err
×
137
                                }
×
138

139
                                if useOwnerRef { // Setup watch using owner references.
×
140
                                        err = c.Watch(
×
141
                                                source.Kind(
×
142
                                                        mgr.GetCache(),
×
143
                                                        client.Object(unstructuredObj),
×
144
                                                        crthandler.TypedEnqueueRequestForOwner[client.Object](mgr.GetScheme(), mgr.GetRESTMapper(), owner, crthandler.OnlyControllerOwner()),
×
145
                                                        predicate.DependentPredicate{}))
×
146
                                        if err != nil {
×
147
                                                return err
×
148
                                        }
×
149
                                } else { // Setup watch using annotations.
×
150
                                        err = c.Watch(
×
151
                                                source.Kind(
×
152
                                                        mgr.GetCache(),
×
153
                                                        client.Object(unstructuredObj),
×
154
                                                        &libhandler.EnqueueRequestForAnnotation[client.Object]{Type: gvkDependent.GroupKind()},
×
155
                                                        predicate.DependentPredicate{}))
×
156
                                        if err != nil {
×
157
                                                return err
×
158
                                        }
×
159
                                }
160
                                m.Lock()
×
161
                                watches[gvkDependent] = struct{}{}
×
162
                                m.Unlock()
×
163
                                log.Info("Watching dependent resource", "ownerApiVersion", r.GVK.GroupVersion(),
×
164
                                        "ownerKind", r.GVK.Kind, "apiVersion", gvkDependent.GroupVersion(), "kind", gvkDependent.Kind)
×
165
                                return nil
×
166
                        }
167

168
                        // List is not actually a resource and therefore cannot have a
169
                        // watch on it. The watch will be on the kinds listed in the list
170
                        // and will therefore need to be handled individually.
171
                        listGVK := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "List"}
×
172
                        if gvk == listGVK {
×
173
                                errListItem := u.EachListItem(func(obj runtime.Object) error {
×
174
                                        return setWatchOnResource(obj)
×
175
                                })
×
176
                                if errListItem != nil {
×
177
                                        return errListItem
×
178
                                }
×
179
                        } else {
×
180
                                err := setWatchOnResource(&u)
×
181
                                if err != nil {
×
182
                                        return err
×
183
                                }
×
184
                        }
185
                }
186
                return nil
×
187
        }
188
        r.releaseHook = releaseHook
×
189
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc