forked from redhat-developer/odo
/
preference.go
487 lines (399 loc) · 15.4 KB
/
preference.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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
package preference
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/odo/cli/ui"
"github.com/openshift/odo/pkg/util"
)
const (
GlobalConfigEnvName = "GLOBALODOCONFIG"
configFileName = "preference.yaml"
preferenceKind = "Preference"
preferenceAPIVersion = "odo.dev/v1alpha1"
//DefaultTimeout for openshift server connection check (in seconds)
DefaultTimeout = 1
// DefaultPushTimeout is the default timeout for pods (in seconds)
DefaultPushTimeout = 240
// UpdateNotificationSetting is the name of the setting controlling update notification
UpdateNotificationSetting = "UpdateNotification"
// UpdateNotificationSettingDescription is human-readable description for the update notification setting
UpdateNotificationSettingDescription = "Flag to control if an update notification is shown or not (Default: true)"
// NamePrefixSetting is the name of the setting controlling name prefix
NamePrefixSetting = "NamePrefix"
// NamePrefixSettingDescription is human-readable description for the name prefix setting
NamePrefixSettingDescription = "Use this value to set a default name prefix (Default: current directory name)"
// TimeoutSetting is the name of the setting controlling timeout for connection check
TimeoutSetting = "Timeout"
// PushTimeoutSetting is the name of the setting controlling PushTimeout
PushTimeoutSetting = "PushTimeout"
// ExperimentalSetting is the name of the setting confrolling exposure of features in development/experimental mode
ExperimentalSetting = "Experimental"
// ExperimentalDescription is human-readable description for the experimental setting
ExperimentalDescription = "Set this value to true to expose features in development/experimental mode"
// PushTargetSetting is the name of the setting confrolling the push target for odo (docker or kube)
PushTargetSetting = "PushTarget"
// PushTargetDescription is human-readable description for the pushtarget setting
PushTargetDescription = "Set this value to 'kube' or 'docker' to tell odo where to push applications to. (Default: kube)"
// Constants for PushTarget values
// DockerPushTarget represents the value of the push target when it's set to Docker
DockerPushTarget = "docker"
// KubePushTarget represents the value of the push target when it's set to Kube
KubePushTarget = "kube"
// CheDevfileRegistryName is the name of Che devfile registry
CheDevfileRegistryName = "CheDevfileRegistry"
// CheDevfileRegistryURL is the URL of Che devfile registry
CheDevfileRegistryURL = "https://che-devfile-registry.openshift.io"
// DefaultDevfileRegistryName is the name of default devfile registry
DefaultDevfileRegistryName = "DefaultDevfileRegistry"
// DefaultDevfileRegistryURL is the URL of default devfile registry
DefaultDevfileRegistryURL = "https://raw.githubusercontent.com/elsony/devfile-registry/master"
)
// TimeoutSettingDescription is human-readable description for the timeout setting
var TimeoutSettingDescription = fmt.Sprintf("Timeout (in seconds) for OpenShift server connection check (Default: %d)", DefaultTimeout)
// PushTimeoutSettingDescription adds a description for PushTimeout
var PushTimeoutSettingDescription = fmt.Sprintf("PushTimeout (in seconds) for waiting for a Pod to come up (Default: %d)", DefaultPushTimeout)
// This value can be provided to set a seperate directory for users 'homedir' resolution
// note for mocking purpose ONLY
var customHomeDir = os.Getenv("CUSTOM_HOMEDIR")
var (
// records information on supported parameters
supportedParameterDescriptions = map[string]string{
UpdateNotificationSetting: UpdateNotificationSettingDescription,
NamePrefixSetting: NamePrefixSettingDescription,
TimeoutSetting: TimeoutSettingDescription,
PushTimeoutSetting: PushTimeoutSettingDescription,
ExperimentalSetting: ExperimentalDescription,
PushTargetSetting: PushTargetDescription,
}
// set-like map to quickly check if a parameter is supported
lowerCaseParameters = util.GetLowerCaseParameters(GetSupportedParameters())
)
// PreferenceInfo wraps the preference and provides helpers to
// serialize it.
type PreferenceInfo struct {
Filename string `yaml:"FileName,omitempty"`
Preference `yaml:",omitempty"`
}
// OdoSettings holds all odo specific configurations
type OdoSettings struct {
// Controls if an update notification is shown or not
UpdateNotification *bool `yaml:"UpdateNotification,omitempty"`
// Holds the prefix part of generated random application name
NamePrefix *string `yaml:"NamePrefix,omitempty"`
// Timeout for OpenShift server connection check
Timeout *int `yaml:"Timeout,omitempty"`
// PushTimeout for OpenShift pod timeout check
PushTimeout *int `yaml:"PushTimeout,omitempty"`
// Experimental for exposing features in development/experimental mode
Experimental *bool `yaml:"Experimental,omitempty"`
// PushTarget for telling odo which platform to push to (either kube or docker)
PushTarget *string `yaml:"PushTarget,omitempty"`
// RegistryList for telling odo to connect to all the registries in the registry list
RegistryList *[]Registry `yaml:"RegistryList,omitempty"`
}
// Registry includes the registry metadata
type Registry struct {
Name string `yaml:"Name,omitempty"`
URL string `yaml:"URL,omitempty"`
}
// Preference stores all the preferences related to odo
type Preference struct {
metav1.TypeMeta `yaml:",inline"`
// Odo settings holds the odo specific global settings
OdoSettings OdoSettings `yaml:"OdoSettings,omitempty"`
}
func getPreferenceFile() (string, error) {
if env, ok := os.LookupEnv(GlobalConfigEnvName); ok {
return env, nil
}
if len(customHomeDir) != 0 {
return filepath.Join(customHomeDir, ".odo", configFileName), nil
}
currentUser, err := user.Current()
if err != nil {
return "", err
}
return filepath.Join(currentUser.HomeDir, ".odo", configFileName), nil
}
// New returns the PreferenceInfo to retain the expected behavior
func New() (*PreferenceInfo, error) {
return NewPreferenceInfo()
}
// NewPreference creates an empty Preference struct with type meta information
func NewPreference() Preference {
return Preference{
TypeMeta: metav1.TypeMeta{
Kind: preferenceKind,
APIVersion: preferenceAPIVersion,
},
}
}
// NewPreferenceInfo gets the PreferenceInfo from preference file and creates the preference file in case it's
// not present
func NewPreferenceInfo() (*PreferenceInfo, error) {
preferenceFile, err := getPreferenceFile()
klog.V(4).Infof("The path for preference file is %+v", preferenceFile)
if err != nil {
return nil, errors.Wrap(err, "unable to get odo preference file")
}
c := PreferenceInfo{
Preference: NewPreference(),
Filename: preferenceFile,
}
// If the preference file doesn't exist then we return with default preference
if _, err = os.Stat(preferenceFile); os.IsNotExist(err) {
return &c, nil
}
err = util.GetFromFile(&c.Preference, c.Filename)
if err != nil {
return nil, err
}
if c.OdoSettings.Experimental != nil && *c.OdoSettings.Experimental {
if c.OdoSettings.RegistryList == nil {
// Handle user has preference file but doesn't use dynamic registry before
defaultRegistryList := []Registry{
{
Name: CheDevfileRegistryName,
URL: CheDevfileRegistryURL,
},
{
Name: DefaultDevfileRegistryName,
URL: DefaultDevfileRegistryURL,
},
}
c.OdoSettings.RegistryList = &defaultRegistryList
}
}
return &c, nil
}
// RegistryHandler handles registry add, update and delete operations
func (c *PreferenceInfo) RegistryHandler(operation string, registryName string, registryURL string, forceFlag bool) error {
var registryList []Registry
var err error
registryExist := false
// Registry list is empty
if c.OdoSettings.RegistryList == nil {
registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL)
if err != nil {
return err
}
} else {
// The target registry exists in the registry list
registryList = *c.OdoSettings.RegistryList
for index, registry := range registryList {
if registry.Name == registryName {
registryExist = true
registryList, err = handleWithRegistryExist(index, registryList, operation, registryName, registryURL, forceFlag)
if err != nil {
return err
}
}
}
// The target registry doesn't exist in the registry list
if !registryExist {
registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL)
if err != nil {
return err
}
}
}
c.OdoSettings.RegistryList = ®istryList
err = util.WriteToFile(&c.Preference, c.Filename)
if err != nil {
return errors.Wrapf(err, "unable to write the configuration of %s operation to preference file", operation)
}
return nil
}
func handleWithoutRegistryExist(registryList []Registry, operation string, registryName string, registryURL string) ([]Registry, error) {
switch operation {
case "add":
registry := Registry{
Name: registryName,
URL: registryURL,
}
registryList = append(registryList, registry)
case "update":
return nil, errors.Errorf("failed to update registry: registry %s doesn't exist", registryName)
case "delete":
return nil, errors.Errorf("failed to delete registry: registry %s doesn't exist", registryName)
}
return registryList, nil
}
func handleWithRegistryExist(index int, registryList []Registry, operation string, registryName string, registryURL string, forceFlag bool) ([]Registry, error) {
switch operation {
case "add":
return nil, errors.Errorf("failed to add registry: registry %s already exists", registryName)
case "update":
if !forceFlag {
if !ui.Proceed(fmt.Sprintf("Are you sure you want to update registry %s", registryName)) {
log.Info("Aborted by the user")
return registryList, nil
}
}
registryList[index].URL = registryURL
log.Info("Successfully updated registry")
case "delete":
if !forceFlag {
if !ui.Proceed(fmt.Sprintf("Are you sure you want to delete registry %s", registryName)) {
log.Info("Aborted by the user")
return registryList, nil
}
}
copy(registryList[index:], registryList[index+1:])
registryList[len(registryList)-1] = Registry{}
registryList = registryList[:len(registryList)-1]
log.Info("Successfully deleted registry")
}
return registryList, nil
}
// SetConfiguration modifies Odo configurations in the config file
// as of now being used for nameprefix, timeout, updatenotification
// TODO: Use reflect to set parameters
func (c *PreferenceInfo) SetConfiguration(parameter string, value string) error {
if p, ok := asSupportedParameter(parameter); ok {
// processing values according to the parameter names
switch p {
case "timeout":
typedval, err := strconv.Atoi(value)
if err != nil {
return errors.Wrapf(err, "unable to set %s to %s", parameter, value)
}
if typedval < 0 {
return errors.Errorf("cannot set timeout to less than 0")
}
c.OdoSettings.Timeout = &typedval
case "pushtimeout":
typedval, err := strconv.Atoi(value)
if err != nil {
return errors.Wrapf(err, "unable to set %s to %s", parameter, value)
}
if typedval < 0 {
return errors.Errorf("cannot set timeout to less than 0")
}
c.OdoSettings.PushTimeout = &typedval
case "updatenotification":
val, err := strconv.ParseBool(strings.ToLower(value))
if err != nil {
return errors.Wrapf(err, "unable to set %s to %s", parameter, value)
}
c.OdoSettings.UpdateNotification = &val
case "nameprefix":
c.OdoSettings.NamePrefix = &value
case "experimental":
val, err := strconv.ParseBool(strings.ToLower(value))
if err != nil {
return errors.Wrapf(err, "unable to set %s to %s", parameter, value)
}
c.OdoSettings.Experimental = &val
case "pushtarget":
val := strings.ToLower(value)
if val != DockerPushTarget && val != KubePushTarget {
return errors.Errorf("cannot set pushtarget to values other than '%s' or '%s'", DockerPushTarget, KubePushTarget)
}
c.OdoSettings.PushTarget = &val
}
} else {
return errors.Errorf("unknown parameter :'%s' is not a parameter in odo preference", parameter)
}
err := util.WriteToFile(&c.Preference, c.Filename)
if err != nil {
return errors.Wrapf(err, "unable to set %s", parameter)
}
return nil
}
// DeleteConfiguration delete Odo configurations in the global config file
// as of now being used for nameprefix, timeout, updatenotification
func (c *PreferenceInfo) DeleteConfiguration(parameter string) error {
if p, ok := asSupportedParameter(parameter); ok {
// processing values according to the parameter names
if err := util.DeleteConfiguration(&c.OdoSettings, p); err != nil {
return err
}
} else {
return errors.Errorf("unknown parameter :'%s' is not a parameter in the odo preference", parameter)
}
err := util.WriteToFile(&c.Preference, c.Filename)
if err != nil {
return errors.Wrapf(err, "unable to set %s", parameter)
}
return nil
}
// IsSet checks if the value is set in the preference
func (c *PreferenceInfo) IsSet(parameter string) bool {
return util.IsSet(c.OdoSettings, parameter)
}
// GetTimeout returns the value of Timeout from config
// and if absent then returns default
func (c *PreferenceInfo) GetTimeout() int {
// default timeout value is 1
if c.OdoSettings.Timeout == nil {
return DefaultTimeout
}
return *c.OdoSettings.Timeout
}
// GetPushTimeout gets the value set by PushTimeout
func (c *PreferenceInfo) GetPushTimeout() int {
// default timeout value is 1
if c.OdoSettings.PushTimeout == nil {
return DefaultPushTimeout
}
return *c.OdoSettings.PushTimeout
}
// GetUpdateNotification returns the value of UpdateNotification from preferences
// and if absent then returns default
func (c *PreferenceInfo) GetUpdateNotification() bool {
if c.OdoSettings.UpdateNotification == nil {
return true
}
return *c.OdoSettings.UpdateNotification
}
// GetNamePrefix returns the value of Prefix from preferences
// and if absent then returns default
func (c *PreferenceInfo) GetNamePrefix() string {
if c.OdoSettings.NamePrefix == nil {
return ""
}
return *c.OdoSettings.NamePrefix
}
// GetExperimental returns the value of Experimental from preferences
// and if absent then returns default
// default value: false, experimental mode is disabled by default
func (c *PreferenceInfo) GetExperimental() bool {
if c.OdoSettings.Experimental == nil {
return false
}
return *c.OdoSettings.Experimental
}
// GetPushTarget returns the value of PushTarget from preferences
// and if absent then returns defualt
// default value: kube, docker push target needs to be manually enabled
func (c *PreferenceInfo) GetPushTarget() string {
if c.OdoSettings.PushTarget == nil {
return KubePushTarget
}
return *c.OdoSettings.PushTarget
}
// FormatSupportedParameters outputs supported parameters and their description
func FormatSupportedParameters() (result string) {
for _, v := range GetSupportedParameters() {
result = result + v + " - " + supportedParameterDescriptions[v] + "\n"
}
return "\nAvailable Parameters:\n" + result
}
// asSupportedParameter checks that the given parameter is supported and returns a lower case version of it if it is
func asSupportedParameter(param string) (string, bool) {
lower := strings.ToLower(param)
return lower, lowerCaseParameters[lower]
}
// GetSupportedParameters returns the name of the supported parameters
func GetSupportedParameters() []string {
return util.GetSortedKeys(supportedParameterDescriptions)
}