Skip to content

Commit 5f3c3a5

Browse files
authored
pkg/helm: new watch file option to support watching dependent resources (#916)
This commit adds a new option to the watches.yaml file called `watchDependentResources`, which enables users to configure whether resources defined by Helm releases are watched for reconciliation. This commit also decouples the loading of the watch file from the creation of manager factories by refactoring the loading of the watch file into a separate `watches` package. This makes it easier to add new watch configurations that are outside the scope of the release manager factory. Lastly, this commit removes helpers that allowed watches to be configured from environment variables. Now that multiple watches are supported and new watch-specific configurations are being added, loading from the environment does not provide the necessary flexibility.
1 parent a38e609 commit 5f3c3a5

File tree

5 files changed

+138
-182
lines changed

5 files changed

+138
-182
lines changed

pkg/helm/controller/reconcile.go

+15-9
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,11 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.
175175
}
176176
status.RemoveCondition(types.ConditionReleaseFailed)
177177

178-
if err := r.releaseHook(installedRelease); err != nil {
179-
log.Error(err, "Failed to run release hook")
180-
return reconcile.Result{}, err
178+
if r.releaseHook != nil {
179+
if err := r.releaseHook(installedRelease); err != nil {
180+
log.Error(err, "Failed to run release hook")
181+
return reconcile.Result{}, err
182+
}
181183
}
182184

183185
log.Info("Installed release")
@@ -212,9 +214,11 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.
212214
}
213215
status.RemoveCondition(types.ConditionReleaseFailed)
214216

215-
if err := r.releaseHook(updatedRelease); err != nil {
216-
log.Error(err, "Failed to run release hook")
217-
return reconcile.Result{}, err
217+
if r.releaseHook != nil {
218+
if err := r.releaseHook(updatedRelease); err != nil {
219+
log.Error(err, "Failed to run release hook")
220+
return reconcile.Result{}, err
221+
}
218222
}
219223

220224
log.Info("Updated release")
@@ -247,9 +251,11 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.
247251
}
248252
status.RemoveCondition(types.ConditionIrreconcilable)
249253

250-
if err := r.releaseHook(expectedRelease); err != nil {
251-
log.Error(err, "Failed to run release hook")
252-
return reconcile.Result{}, err
254+
if r.releaseHook != nil {
255+
if err := r.releaseHook(expectedRelease); err != nil {
256+
log.Error(err, "Failed to run release hook")
257+
return reconcile.Result{}, err
258+
}
253259
}
254260

255261
log.Info("Reconciled release")

pkg/helm/release/manager_factory.go

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ type managerFactory struct {
4949
chartDir string
5050
}
5151

52+
// NewManagerFactory returns a new Helm manager factory capable of installing and uninstalling releases.
53+
func NewManagerFactory(storageBackend *storage.Storage, tillerKubeClient *kube.Client, chartDir string) ManagerFactory {
54+
return &managerFactory{storageBackend, tillerKubeClient, chartDir}
55+
}
56+
5257
func (f managerFactory) NewManager(r *unstructured.Unstructured) Manager {
5358
return f.newManagerForCR(r)
5459
}

pkg/helm/release/new.go

-168
This file was deleted.

pkg/helm/run.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/operator-framework/operator-sdk/pkg/helm/controller"
2525
hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags"
2626
"github.com/operator-framework/operator-sdk/pkg/helm/release"
27+
"github.com/operator-framework/operator-sdk/pkg/helm/watches"
2728
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
2829
"github.com/operator-framework/operator-sdk/pkg/leader"
2930
sdkVersion "github.com/operator-framework/operator-sdk/version"
@@ -80,20 +81,20 @@ func Run(flags *hoflags.HelmOperatorFlags) error {
8081
return err
8182
}
8283

83-
factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, flags.WatchesFile)
84+
watches, err := watches.Load(flags.WatchesFile)
8485
if err != nil {
8586
log.Error(err, "Failed to create new manager factories.")
8687
return err
8788
}
8889

89-
for gvk, factory := range factories {
90+
for _, w := range watches {
9091
// Register the controller with the factory.
9192
err := controller.Add(mgr, controller.WatchOptions{
9293
Namespace: namespace,
93-
GVK: gvk,
94-
ManagerFactory: factory,
94+
GVK: w.GroupVersionKind,
95+
ManagerFactory: release.NewManagerFactory(storageBackend, tillerKubeClient, w.ChartDir),
9596
ReconcilePeriod: flags.ReconcilePeriod,
96-
WatchDependentResources: true,
97+
WatchDependentResources: w.WatchDependentResources,
9798
})
9899
if err != nil {
99100
log.Error(err, "Failed to add manager factory to controller.")

pkg/helm/watches/watches.go

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2019 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 watches
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"io/ioutil"
21+
22+
yaml "gopkg.in/yaml.v2"
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
"k8s.io/helm/pkg/chartutil"
25+
)
26+
27+
// Watch defines options for configuring a watch for a Helm-based
28+
// custom resource.
29+
type Watch struct {
30+
GroupVersionKind schema.GroupVersionKind
31+
ChartDir string
32+
WatchDependentResources bool
33+
}
34+
35+
type yamlWatch struct {
36+
Group string `yaml:"group"`
37+
Version string `yaml:"version"`
38+
Kind string `yaml:"kind"`
39+
Chart string `yaml:"chart"`
40+
WatchDependentResources bool `yaml:"watchDependentResources"`
41+
}
42+
43+
func (w *yamlWatch) UnmarshalYAML(unmarshal func(interface{}) error) error {
44+
// by default, the operator will watch dependent resources
45+
w.WatchDependentResources = true
46+
47+
// hide watch data in plain struct to prevent unmarshal from calling
48+
// UnmarshalYAML again
49+
type plain yamlWatch
50+
51+
return unmarshal((*plain)(w))
52+
}
53+
54+
// Load loads a slice of Watches from the watch file at `path`. For each entry
55+
// in the watches file, it verifies the configuration. If an error is
56+
// encountered loading the file or verifying the configuration, it will be
57+
// returned.
58+
func Load(path string) ([]Watch, error) {
59+
b, err := ioutil.ReadFile(path)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
yamlWatches := []yamlWatch{}
65+
err = yaml.Unmarshal(b, &yamlWatches)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
watches := []Watch{}
71+
watchesMap := make(map[schema.GroupVersionKind]Watch)
72+
for _, w := range yamlWatches {
73+
gvk := schema.GroupVersionKind{
74+
Group: w.Group,
75+
Version: w.Version,
76+
Kind: w.Kind,
77+
}
78+
79+
if err := verifyGVK(gvk); err != nil {
80+
return nil, fmt.Errorf("invalid GVK: %s: %s", gvk, err)
81+
}
82+
83+
if _, err := chartutil.IsChartDir(w.Chart); err != nil {
84+
return nil, fmt.Errorf("invalid chart directory %s: %s", w.Chart, err)
85+
}
86+
87+
if _, ok := watchesMap[gvk]; ok {
88+
return nil, fmt.Errorf("duplicate GVK: %s", gvk)
89+
}
90+
watch := Watch{
91+
GroupVersionKind: gvk,
92+
ChartDir: w.Chart,
93+
WatchDependentResources: w.WatchDependentResources,
94+
}
95+
watchesMap[gvk] = watch
96+
watches = append(watches, watch)
97+
}
98+
return watches, nil
99+
}
100+
101+
func verifyGVK(gvk schema.GroupVersionKind) error {
102+
// A GVK without a group is valid. Certain scenarios may cause a GVK
103+
// without a group to fail in other ways later in the initialization
104+
// process.
105+
if gvk.Version == "" {
106+
return errors.New("version must not be empty")
107+
}
108+
if gvk.Kind == "" {
109+
return errors.New("kind must not be empty")
110+
}
111+
return nil
112+
}

0 commit comments

Comments
 (0)