forked from aws-controllers-k8s/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
323 lines (295 loc) · 11.6 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package config
import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/jaypipes/envutil"
flag "github.com/spf13/pflag"
"go.uber.org/zap/zapcore"
ctrlrt "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
acktags "github.com/aws-controllers-k8s/runtime/pkg/tags"
)
const (
flagEnableLeaderElection = "enable-leader-election"
flagMetricAddr = "metrics-addr"
flagEnableDevLogging = "enable-development-logging"
flagAWSRegion = "aws-region"
flagAWSEndpointURL = "aws-endpoint-url"
flagAWSIdentityEndpointURL = "aws-identity-endpoint-url"
flagUnsafeAWSEndpointURLs = "allow-unsafe-aws-endpoint-urls"
flagLogLevel = "log-level"
flagResourceTags = "resource-tags"
flagWatchNamespace = "watch-namespace"
flagEnableWebhookServer = "enable-webhook-server"
flagWebhookServerAddr = "webhook-server-addr"
flagDeletionPolicy = "deletion-policy"
flagReconcileDefaultResyncSeconds = "reconcile-default-resync-seconds"
flagReconcileResourceResyncSeconds = "reconcile-resource-resync-seconds"
envVarAWSRegion = "AWS_REGION"
)
var (
defaultResourceTags = []string{
fmt.Sprintf("services.k8s.aws/controller-version=%s-%s",
acktags.ServiceAliasTagFormat,
acktags.ControllerVersionTagFormat,
),
fmt.Sprintf("services.k8s.aws/namespace=%s",
acktags.NamespaceTagFormat,
),
}
defaultLogLevel = zapcore.InfoLevel
)
// Config contains configuration options for ACK service controllers
type Config struct {
MetricsAddr string
EnableLeaderElection bool
EnableDevelopmentLogging bool
AccountID string
Region string
IdentityEndpointURL string
EndpointURL string
AllowUnsafeEndpointURL bool
LogLevel string
ResourceTags []string
WatchNamespace string
EnableWebhookServer bool
WebhookServerAddr string
DeletionPolicy ackv1alpha1.DeletionPolicy
ReconcileDefaultResyncSeconds int
ReconcileResourceResyncSeconds []string
}
// BindFlags defines CLI/runtime configuration options
func (cfg *Config) BindFlags() {
flag.StringVar(
&cfg.MetricsAddr, flagMetricAddr,
"0.0.0.0:8080",
"The address the metric endpoint binds to.",
)
flag.BoolVar(
&cfg.EnableWebhookServer, flagEnableWebhookServer,
false,
"Enable webhook server for controller manager.",
)
flag.StringVar(
&cfg.WebhookServerAddr, flagWebhookServerAddr,
"0.0.0.0:9433",
"The address the webhook endpoint binds to.",
)
flag.BoolVar(
&cfg.EnableLeaderElection, flagEnableLeaderElection,
false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.",
)
flag.BoolVar(
&cfg.EnableDevelopmentLogging, flagEnableDevLogging,
false,
"Configures the logger to use a Zap development config (encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn, no sampling), "+
"otherwise a Zap production config will be used (encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error), sampling).",
)
flag.StringVar(
&cfg.Region, flagAWSRegion,
envutil.WithDefault(envVarAWSRegion, ""),
"The AWS Region in which the service controller will create its resources",
)
flag.StringVar(
&cfg.EndpointURL, flagAWSEndpointURL,
"",
"The AWS endpoint URL the service controller will use to create its resources. This is an optional"+
" flag that can be used to override the default behaviour of aws-sdk-go that constructs endpoint URLs"+
" automatically based on service and region",
)
flag.StringVar(
&cfg.IdentityEndpointURL, flagAWSIdentityEndpointURL,
"",
"The AWS endpoint URL the service controller will use to gather information from STS. This is an optional"+
" flag that can be used to override the default behaviour of aws-sdk-go that constructs endpoint URLs"+
" automatically based on service and region",
)
flag.BoolVar(
&cfg.AllowUnsafeEndpointURL, flagUnsafeAWSEndpointURLs,
false,
"Allow an unsafe AWS endpoint URL over http",
)
flag.StringVar(
&cfg.LogLevel, flagLogLevel,
"info",
"The log level. The default is info. The options are: debug, info, warn, error, dpanic, panic, fatal",
)
flag.StringSliceVar(
&cfg.ResourceTags, flagResourceTags,
defaultResourceTags,
"Configures the ACK service controller to always set key/value pairs tags on resources that it manages.",
)
flag.StringVar(
&cfg.WatchNamespace, flagWatchNamespace,
"",
"Specific namespace the service controller will watch for object creation from CRD. "+
" By default it will listen to all namespaces",
)
flag.Var(
&cfg.DeletionPolicy, flagDeletionPolicy,
"The default deletion policy for all resources managed by the controller",
)
flag.IntVar(
&cfg.ReconcileDefaultResyncSeconds, flagReconcileDefaultResyncSeconds,
60,
"The default duration, in seconds, to wait before resyncing desired state of custom resources. "+
"This value is used if no resource-specific override has been specified. Default is 60 seconds.",
)
flag.StringArrayVar(
&cfg.ReconcileResourceResyncSeconds, flagReconcileResourceResyncSeconds,
[]string{},
"A Key/Value list of strings representing the reconcile resync configuration for each resource. This"+
" configuration maps resource kinds to drift remediation periods in seconds. If provided, "+
" resource-specific resync periods take precedence over the default period.",
)
}
// SetupLogger initializes the logger used in the service controller
func (cfg *Config) SetupLogger() {
lvl := defaultLogLevel
lvl.UnmarshalText([]byte(cfg.LogLevel))
zapOptions := zap.Options{
Development: cfg.EnableDevelopmentLogging,
Level: lvl,
TimeEncoder: zapcore.ISO8601TimeEncoder,
}
ctrlrt.SetLogger(zap.New(zap.UseFlagOptions(&zapOptions)))
}
// SetAWSAccountID uses sts GetCallerIdentity API to find AWS AccountId and set
// in Config
func (cfg *Config) SetAWSAccountID() error {
awsCfg := aws.Config{}
if cfg.IdentityEndpointURL != "" {
awsCfg.Endpoint = aws.String(cfg.IdentityEndpointURL)
}
// use sts to find AWS AccountId
session, err := session.NewSession(&awsCfg)
if err != nil {
return fmt.Errorf("unable to create session: %v", err)
}
client := sts.New(session)
res, err := client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
if err != nil {
return fmt.Errorf("unable to get caller identity: %v", err)
}
cfg.AccountID = *res.Account
return nil
}
// Validate ensures the options are valid
func (cfg *Config) Validate() error {
if cfg.Region == "" {
return errors.New("unable to start service controller as AWS region is missing. Please pass --aws-region flag or set AWS_REGION environment variable")
}
if cfg.EndpointURL != "" {
serviceEndpoint, err := url.Parse(cfg.EndpointURL)
if err != nil {
return errors.New("invalid service endpoint. Please refer to " +
"https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html for more details")
}
// Throw an error if URL is unsafe and config.AllowUnsafeEndpointURL is not set accordingly
if err := cfg.checkUnsafeEndpoint(serviceEndpoint); err != nil {
return err
}
}
if cfg.IdentityEndpointURL != "" {
identityEndpoint, err := url.Parse(cfg.IdentityEndpointURL)
if err != nil {
return errors.New("invalid identity endpoint. Please refer to " +
"https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html for more details")
}
// Throw an error if URL is unsafe and config.AllowUnsafeEndpointURL is not set accordingly
if err := cfg.checkUnsafeEndpoint(identityEndpoint); err != nil {
return err
}
}
if err := cfg.SetAWSAccountID(); err != nil {
return fmt.Errorf("unable to determine account ID: %v", err)
}
if cfg.EnableWebhookServer && cfg.WebhookServerAddr == "" {
return errors.New("empty webhook server address")
}
if cfg.DeletionPolicy == "" {
cfg.DeletionPolicy = ackv1alpha1.DeletionPolicyDelete
}
if cfg.ReconcileDefaultResyncSeconds < 0 {
return fmt.Errorf("invalid value for flag '%s': resync seconds default must be greater than 0", flagReconcileDefaultResyncSeconds)
}
_, err := cfg.ParseReconcileResourceResyncSeconds()
if err != nil {
return fmt.Errorf("invalid value for flag '%s': %v", flagReconcileResourceResyncSeconds, err)
}
return nil
}
func (cfg *Config) checkUnsafeEndpoint(endpoint *url.URL) error {
if !cfg.AllowUnsafeEndpointURL {
if endpoint.Scheme != "https" && endpoint.Host != "" {
return errors.New("using an unsafe endpoint is not allowed. Please review the controller configuration")
}
}
return nil
}
// ParseReconcileResourceResyncSeconds parses the values of the --reconcile-resource-resync-seconds
// flag and returns a map that maps resource names to resync periods.
// The flag arguments are expected to have the format "resource=seconds", where "resource" is the
// name of the resource and "seconds" is the number of seconds that the reconciler should wait before
// reconciling the resource again.
func (cfg *Config) ParseReconcileResourceResyncSeconds() (map[string]time.Duration, error) {
resourceResyncPeriods := make(map[string]time.Duration, len(cfg.ReconcileResourceResyncSeconds))
for _, resourceResyncSecondsFlag := range cfg.ReconcileResourceResyncSeconds {
// Parse the resource name and resync period from the flag argument
resourceName, resyncSeconds, err := parseReconcileFlagArgument(resourceResyncSecondsFlag)
if err != nil {
return nil, fmt.Errorf("error parsing flag argument '%v': %v. Expected format: resource=seconds", resourceResyncSecondsFlag, err)
}
resourceResyncPeriods[strings.ToLower(resourceName)] = time.Duration(resyncSeconds)
}
return resourceResyncPeriods, nil
}
// parseReconcileFlagArgument parses a flag argument of the form "key=value" into
// its individual elements. The key must be a non-empty string and the value must be
// a non-empty positive integer. If the flag argument is not in the expected format
// or has invalid elements, an error is returned.
//
// The function returns the parsed key and value as separate elements.
func parseReconcileFlagArgument(flagArgument string) (string, int, error) {
delimiter := "="
elements := strings.Split(flagArgument, delimiter)
if len(elements) != 2 {
return "", 0, fmt.Errorf("invalid flag argument format: expected key=value")
}
if elements[0] == "" {
return "", 0, fmt.Errorf("missing key in flag argument")
}
if elements[1] == "" {
return "", 0, fmt.Errorf("missing value in flag argument")
}
resyncSeconds, err := strconv.Atoi(elements[1])
if err != nil {
return "", 0, fmt.Errorf("invalid value in flag argument: %v", err)
}
if resyncSeconds < 0 {
return "", 0, fmt.Errorf("invalid value in flag argument: expected non-negative integer, got %d", resyncSeconds)
}
return elements[0], resyncSeconds, nil
}