Skip to content

Commit c9e82fe

Browse files
committed
This commit fixes manager component configation for
ansible-operator and helm-operator by adding the underlying reading and Manager option updating functionality. This was missing when the config file was originally added to both ansible/v1 and helm/v1 plugins. --config accepts a path to this config file. internal/{ansible,helm}: add --config flag, and configure existing flags to be applied on top of manager Options internal/cmd/{ansible,helm}: apply config file data prior to flag data Signed-off-by: Eric Stroczynski <ericstroczynski@gmail.com>
1 parent 06c31c9 commit c9e82fe

File tree

7 files changed

+296
-81
lines changed

7 files changed

+296
-81
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
entries:
2+
- description: >
3+
(ansible/v1, helm/v1) correctly support configuring the manager by file using the
4+
`--config` flag. This flag was not added to either ansible-/helm-operator binary
5+
when file support was originally added.
6+
kind: bugfix
7+
migration:
8+
header: Add the manager config patch to config/default/kustomization.yaml
9+
body: >
10+
The scaffolded `--config` flag was not added to either ansible-/helm-operator binary
11+
when config file support was originally added, so does not currently work.
12+
The `--config` flag was added to both binaries to support configuration by file;
13+
this method of configuration only applies to the underlying
14+
[controller manager](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager#Manager),
15+
not the operator as a whole. To configure the operator's Deployment with this config data,
16+
add the following to config/default/kustomization.yaml:
17+
18+
```diff
19+
# If you want your controller-manager to expose the /metrics
20+
# endpoint w/o any authn/z, please comment the following line.
21+
- manager_auth_proxy_patch.yaml
22+
+
23+
+# Mount the controller config file for loading manager configurations
24+
+# through a ComponentConfig type
25+
+#- manager_config_patch.yaml
26+
```

internal/ansible/flags/flag.go

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"time"
2020

2121
"github.com/spf13/pflag"
22+
"k8s.io/client-go/tools/leaderelection/resourcelock"
23+
"sigs.k8s.io/controller-runtime/pkg/manager"
2224
)
2325

2426
// Flags - Options to be used by an ansible operator
@@ -37,18 +39,23 @@ type Flags struct {
3739
LeaderElectionNamespace string
3840
GracefulShutdownTimeout time.Duration
3941
AnsibleArgs string
42+
43+
// Path to a controller-runtime componentconfig file.
44+
// If this is empty, use default values.
45+
ManagerConfigPath string
46+
47+
// If not nil, used to deduce which flags were set in the CLI.
48+
flagSet *pflag.FlagSet
4049
}
4150

42-
const AnsibleRolesPathEnvVar = "ANSIBLE_ROLES_PATH"
43-
const AnsibleCollectionsPathEnvVar = "ANSIBLE_COLLECTIONS_PATH"
51+
const (
52+
AnsibleRolesPathEnvVar = "ANSIBLE_ROLES_PATH"
53+
AnsibleCollectionsPathEnvVar = "ANSIBLE_COLLECTIONS_PATH"
54+
)
4455

4556
// AddTo - Add the ansible operator flags to the the flagset
4657
func (f *Flags) AddTo(flagSet *pflag.FlagSet) {
47-
flagSet.DurationVar(&f.ReconcilePeriod,
48-
"reconcile-period",
49-
time.Minute,
50-
"Default reconcile period for controllers",
51-
)
58+
// Ansible flags.
5259
flagSet.StringVar(&f.WatchesFile,
5360
"watches-file",
5461
"./watches.yaml",
@@ -59,11 +66,6 @@ func (f *Flags) AddTo(flagSet *pflag.FlagSet) {
5966
true,
6067
"The ansible operator will inject owner references unless this flag is false",
6168
)
62-
flagSet.IntVar(&f.MaxConcurrentReconciles,
63-
"max-concurrent-reconciles",
64-
runtime.NumCPU(),
65-
"Maximum number of concurrent reconciles for controllers. Overridden by environment variable.",
66-
)
6769
flagSet.IntVar(&f.AnsibleVerbosity,
6870
"ansible-verbosity",
6971
2,
@@ -77,9 +79,36 @@ func (f *Flags) AddTo(flagSet *pflag.FlagSet) {
7779
flagSet.StringVar(&f.AnsibleCollectionsPath,
7880
"ansible-collections-path",
7981
"",
80-
"Path to installed Ansible Collections. If set, collections should be located in {{value}}/ansible_collections/. If unset, collections are assumed to be in ~/.ansible/collections or /usr/share/ansible/collections.",
82+
"Path to installed Ansible Collections. If set, collections should be located in {{value}}/ansible_collections/. "+
83+
"If unset, collections are assumed to be in ~/.ansible/collections or /usr/share/ansible/collections.",
84+
)
85+
flagSet.StringVar(&f.AnsibleArgs,
86+
"ansible-args",
87+
"",
88+
"Ansible args. Allows user to specify arbitrary arguments for ansible-based operators.",
8189
)
82-
// todo:remove it for 2.0.0
90+
91+
// Controller flags.
92+
flagSet.DurationVar(&f.ReconcilePeriod,
93+
"reconcile-period",
94+
time.Minute,
95+
"Default reconcile period for controllers",
96+
)
97+
flagSet.IntVar(&f.MaxConcurrentReconciles,
98+
"max-concurrent-reconciles",
99+
runtime.NumCPU(),
100+
"Maximum number of concurrent reconciles for controllers. Overridden by environment variable.",
101+
)
102+
103+
// Controller manager flags.
104+
flagSet.StringVar(&f.ManagerConfigPath,
105+
"manager-config",
106+
"",
107+
"The controller will load its initial configuration from this file. "+
108+
"Omit this flag to use the default configuration values. "+
109+
"Command-line flags override configuration from this file.",
110+
)
111+
// TODO(2.0.0): remove
83112
flagSet.StringVar(&f.MetricsBindAddress,
84113
"metrics-addr",
85114
":8080",
@@ -91,15 +120,14 @@ func (f *Flags) AddTo(flagSet *pflag.FlagSet) {
91120
":8080",
92121
"The address the metric endpoint binds to",
93122
)
94-
// todo: for Go/Helm the port used is: 8081
123+
// TODO(2.0.0): for Go/Helm the port used is: 8081
95124
// update it to keep the project aligned to the other
96-
// types for 2.0
97125
flagSet.StringVar(&f.ProbeAddr,
98126
"health-probe-bind-address",
99127
":6789",
100128
"The address the probe endpoint binds to.",
101129
)
102-
// todo:remove it for 2.0.0
130+
// TODO(2.0.0): remove
103131
flagSet.BoolVar(&f.LeaderElection,
104132
"enable-leader-election",
105133
false,
@@ -131,9 +159,43 @@ func (f *Flags) AddTo(flagSet *pflag.FlagSet) {
131159
"The amount of time that will be spent waiting"+
132160
" for runners to gracefully exit.",
133161
)
134-
flagSet.StringVar(&f.AnsibleArgs,
135-
"ansible-args",
136-
"",
137-
"Ansible args. Allows user to specify arbitrary arguments for ansible-based operators.",
138-
)
162+
}
163+
164+
// ToManagerOptions uses the flag set in f to configure options.
165+
// Values of options take precedence over flag defaults,
166+
// as values are assume to have been explicitly set.
167+
func (f *Flags) ToManagerOptions(options manager.Options) manager.Options {
168+
// Alias FlagSet.Changed so options are still updated when fields are empty.
169+
changed := func(flagName string) bool {
170+
return f.flagSet.Changed(flagName)
171+
}
172+
if f.flagSet == nil {
173+
changed = func(flagName string) bool { return false }
174+
}
175+
176+
// TODO(2.0.0): remove metrics-addr
177+
if changed("metrics-bind-address") || changed("metrics-addr") || options.MetricsBindAddress == "" {
178+
options.MetricsBindAddress = f.MetricsBindAddress
179+
}
180+
if changed("health-probe-bind-address") || options.HealthProbeBindAddress == "" {
181+
options.HealthProbeBindAddress = f.ProbeAddr
182+
}
183+
// TODO(2.0.0): remove enable-leader-election
184+
if changed("leader-elect") || changed("enable-leader-election") || !options.LeaderElection {
185+
options.LeaderElection = f.LeaderElection
186+
}
187+
if changed("leader-election-id") || options.LeaderElectionID == "" {
188+
options.LeaderElectionID = f.LeaderElectionID
189+
}
190+
if changed("leader-election-namespace") || options.LeaderElectionNamespace == "" {
191+
options.LeaderElectionNamespace = f.LeaderElectionNamespace
192+
}
193+
if options.LeaderElectionResourceLock == "" {
194+
options.LeaderElectionResourceLock = resourcelock.ConfigMapsResourceLock
195+
}
196+
if changed("graceful-shutdown-timeout") || options.GracefulShutdownTimeout == nil {
197+
options.GracefulShutdownTimeout = &f.GracefulShutdownTimeout
198+
}
199+
200+
return options
139201
}

internal/cmd/ansible-operator/run/cmd.go

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525

2626
"github.com/spf13/cobra"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28-
"k8s.io/client-go/tools/leaderelection/resourcelock"
28+
ctrl "sigs.k8s.io/controller-runtime"
2929
"sigs.k8s.io/controller-runtime/pkg/cache"
3030
"sigs.k8s.io/controller-runtime/pkg/client/config"
3131
"sigs.k8s.io/controller-runtime/pkg/healthz"
@@ -82,25 +82,43 @@ func run(cmd *cobra.Command, f *flags.Flags) {
8282
printVersion()
8383
metrics.RegisterBuildInfo(crmetrics.Registry)
8484

85+
// Load config options from the config at f.ManagerConfigPath.
86+
// These options will not override those set by flags.
87+
var (
88+
options manager.Options
89+
err error
90+
)
91+
if f.ManagerConfigPath != "" {
92+
cfgLoader := ctrl.ConfigFile().AtPath(f.ManagerConfigPath)
93+
if options, err = options.AndFrom(cfgLoader); err != nil {
94+
log.Error(err, "Unable to load the manager config file")
95+
os.Exit(1)
96+
}
97+
}
98+
exitIfUnsupported(options)
99+
85100
cfg, err := config.GetConfig()
86101
if err != nil {
87102
log.Error(err, "Failed to get config.")
88103
os.Exit(1)
89104
}
90105

106+
// TODO(2.0.0): remove
91107
// Deprecated: OPERATOR_NAME environment variable is an artifact of the
92108
// legacy operator-sdk project scaffolding. Flag `--leader-election-id`
93109
// should be used instead.
94110
if operatorName, found := os.LookupEnv("OPERATOR_NAME"); found {
95111
log.Info("Environment variable OPERATOR_NAME has been deprecated, use --leader-election-id instead.")
96-
if cmd.Flags().Lookup("leader-election-id").Changed {
112+
if cmd.Flags().Changed("leader-election-id") {
97113
log.Info("Ignoring OPERATOR_NAME environment variable since --leader-election-id is set")
98-
} else {
99-
f.LeaderElectionID = operatorName
114+
} else if options.LeaderElectionID == "" {
115+
// Only set leader election ID using OPERATOR_NAME if unset everywhere else,
116+
// since this env var is deprecated.
117+
options.LeaderElectionID = operatorName
100118
}
101119
}
102120

103-
//todo: remove the following checks for 2.0.0 they are required just because of the flags deprecation
121+
//TODO(2.0.0): remove the following checks. they are required just because of the flags deprecation
104122
if cmd.Flags().Changed("leader-elect") && cmd.Flags().Changed("enable-leader-election") {
105123
log.Error(errors.New("only one of --leader-elect and --enable-leader-election may be set"), "invalid flags usage")
106124
os.Exit(1)
@@ -113,20 +131,15 @@ func run(cmd *cobra.Command, f *flags.Flags) {
113131

114132
// Set default manager options
115133
// TODO: probably should expose the host & port as an environment variables
116-
options := manager.Options{
117-
MetricsBindAddress: f.MetricsBindAddress,
118-
HealthProbeBindAddress: f.ProbeAddr,
119-
LeaderElection: f.LeaderElection,
120-
LeaderElectionID: f.LeaderElectionID,
121-
LeaderElectionResourceLock: resourcelock.ConfigMapsResourceLock,
122-
LeaderElectionNamespace: f.LeaderElectionNamespace,
123-
ClientBuilder: clientbuilder.NewUnstructedCached(),
124-
GracefulShutdownTimeout: &f.GracefulShutdownTimeout,
134+
options = f.ToManagerOptions(options)
135+
if options.ClientBuilder == nil {
136+
options.ClientBuilder = clientbuilder.NewUnstructedCached()
125137
}
126138

127139
namespace, found := os.LookupEnv(k8sutil.WatchNamespaceEnvVar)
128140
log = log.WithValues("Namespace", namespace)
129141
if found {
142+
log.V(1).Info("Setting namespace with value in %s", k8sutil.WatchNamespaceEnvVar)
130143
if namespace == metav1.NamespaceAll {
131144
log.Info("Watching all namespaces.")
132145
options.Namespace = metav1.NamespaceAll
@@ -139,9 +152,9 @@ func run(cmd *cobra.Command, f *flags.Flags) {
139152
options.Namespace = namespace
140153
}
141154
}
142-
} else {
143-
log.Info(fmt.Sprintf("%v environment variable not set. Watching all namespaces.",
144-
k8sutil.WatchNamespaceEnvVar))
155+
} else if options.Namespace == "" {
156+
log.Info(fmt.Sprintf("Watch namespaces not configured by environment variable %s or file. "+
157+
"Watching all namespaces.", k8sutil.WatchNamespaceEnvVar))
145158
options.Namespace = metav1.NamespaceAll
146159
}
147160

@@ -202,7 +215,7 @@ func run(cmd *cobra.Command, f *flags.Flags) {
202215
}, w.Blacklist)
203216
}
204217

205-
// todo: remove when a upper version be bumped
218+
// TODO(2.0.0): remove
206219
err = mgr.AddHealthzCheck("ping", healthz.Ping)
207220
if err != nil {
208221
log.Error(err, "Failed to add Healthz check.")
@@ -219,7 +232,7 @@ func run(cmd *cobra.Command, f *flags.Flags) {
219232
RESTMapper: mgr.GetRESTMapper(),
220233
ControllerMap: cMap,
221234
OwnerInjection: f.InjectOwnerRef,
222-
WatchedNamespaces: []string{namespace},
235+
WatchedNamespaces: strings.Split(namespace, ","),
223236
})
224237
if err != nil {
225238
log.Error(err, "Error starting proxy.")
@@ -240,6 +253,27 @@ func run(cmd *cobra.Command, f *flags.Flags) {
240253
log.Info("Exiting.")
241254
}
242255

256+
// exitIfUnsupported prints an error containing unsupported field names and exits
257+
// if any of those fields are not their default values.
258+
func exitIfUnsupported(options manager.Options) {
259+
var keys []string
260+
// The below options are webhook-specific, which is not supported by ansible.
261+
if options.CertDir != "" {
262+
keys = append(keys, "certDir")
263+
}
264+
if options.Host != "" {
265+
keys = append(keys, "host")
266+
}
267+
if options.Port != 0 {
268+
keys = append(keys, "port")
269+
}
270+
271+
if len(keys) > 0 {
272+
log.Error(fmt.Errorf("%s set in manager options", strings.Join(keys, ", ")), "unsupported fields")
273+
os.Exit(1)
274+
}
275+
}
276+
243277
// getAnsibleDebugLog return the value from the ANSIBLE_DEBUG_LOGS it order to
244278
// print the full Ansible logs
245279
func getAnsibleDebugLog() bool {

0 commit comments

Comments
 (0)