...

Source file src/github.com/redhat-developer/odo/pkg/preference/implem.go

Documentation: github.com/redhat-developer/odo/pkg/preference

     1  package preference
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/user"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/redhat-developer/odo/pkg/api"
    14  	envcontext "github.com/redhat-developer/odo/pkg/config/context"
    15  	"github.com/redhat-developer/odo/pkg/log"
    16  	"github.com/redhat-developer/odo/pkg/odo/cli/ui"
    17  	"github.com/redhat-developer/odo/pkg/util"
    18  
    19  	dfutil "github.com/devfile/library/v2/pkg/util"
    20  
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	"k8s.io/klog"
    23  	kpointer "k8s.io/utils/pointer"
    24  )
    25  
    26  // odoSettings holds all odo specific configurations
    27  // these configurations are applicable across the odo components
    28  type odoSettings struct {
    29  	// Controls if an update notification is shown or not
    30  	UpdateNotification *bool `yaml:"UpdateNotification,omitempty"`
    31  
    32  	// Timeout for server connection check
    33  	Timeout *time.Duration `yaml:"Timeout,omitempty"`
    34  
    35  	// PushTimeout for pod timeout check
    36  	PushTimeout *time.Duration `yaml:"PushTimeout,omitempty"`
    37  
    38  	// RegistryList for telling odo to connect to all the registries in the registry list
    39  	RegistryList *[]Registry `yaml:"RegistryList,omitempty"`
    40  
    41  	// RegistryCacheTime how long odo should cache information from registry
    42  	RegistryCacheTime *time.Duration `yaml:"RegistryCacheTime,omitempty"`
    43  
    44  	// Ephemeral if true creates odo emptyDir to store odo source code
    45  	Ephemeral *bool `yaml:"Ephemeral,omitempty"`
    46  
    47  	// ConsentTelemetry if true collects telemetry for odo
    48  	ConsentTelemetry *bool `yaml:"ConsentTelemetry,omitempty"`
    49  
    50  	// ImageRegistry is the image registry to which relative image names in Devfile Image Components will be pushed to.
    51  	// This will also serve as the base path for replacing matching images in other components like Container and Kubernetes/OpenShift ones.
    52  	ImageRegistry *string `yaml:"ImageRegistry,omitempty"`
    53  }
    54  
    55  // Registry includes the registry metadata
    56  type Registry struct {
    57  	Name   string `yaml:"Name,omitempty" json:"name"`
    58  	URL    string `yaml:"URL,omitempty" json:"url"`
    59  	Secure bool   `json:"secure"`
    60  }
    61  
    62  // Preference stores all the preferences related to odo
    63  type Preference struct {
    64  	metav1.TypeMeta `yaml:",inline"`
    65  
    66  	// Odo settings holds the odo specific global settings
    67  	OdoSettings odoSettings `yaml:"OdoSettings,omitempty"`
    68  }
    69  
    70  // preferenceInfo wraps the preference and provides helpers to
    71  // serialize it.
    72  type preferenceInfo struct {
    73  	Filename   string `yaml:"FileName,omitempty"`
    74  	Preference `yaml:",omitempty"`
    75  }
    76  
    77  var _ Client = (*preferenceInfo)(nil)
    78  
    79  func getPreferenceFile(ctx context.Context) (string, error) {
    80  	envConfig := envcontext.GetEnvConfig(ctx)
    81  	if envConfig.Globalodoconfig != nil {
    82  		return *envConfig.Globalodoconfig, nil
    83  	}
    84  
    85  	if len(customHomeDir) != 0 {
    86  		return filepath.Join(customHomeDir, ".odo", configFileName), nil
    87  	}
    88  
    89  	currentUser, err := user.Current()
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  	return filepath.Join(currentUser.HomeDir, ".odo", configFileName), nil
    94  }
    95  
    96  func NewClient(ctx context.Context) (Client, error) {
    97  	return newPreferenceInfo(ctx)
    98  }
    99  
   100  // newPreference creates an empty Preference struct with type meta information
   101  func newPreference() Preference {
   102  	return Preference{
   103  		TypeMeta: metav1.TypeMeta{
   104  			Kind:       preferenceKind,
   105  			APIVersion: preferenceAPIVersion,
   106  		},
   107  	}
   108  }
   109  
   110  // newPreferenceInfo gets the PreferenceInfo from preference file
   111  // or returns default PreferenceInfo if preference file does not exist
   112  func newPreferenceInfo(ctx context.Context) (*preferenceInfo, error) {
   113  	preferenceFile, err := getPreferenceFile(ctx)
   114  	klog.V(4).Infof("The path for preference file is %+v", preferenceFile)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	c := preferenceInfo{
   120  		Preference: newPreference(),
   121  		Filename:   preferenceFile,
   122  	}
   123  
   124  	// Default devfile registry
   125  	defaultRegistryList := []Registry{
   126  		{
   127  			Name:   DefaultDevfileRegistryName,
   128  			URL:    DefaultDevfileRegistryURL,
   129  			Secure: false,
   130  		},
   131  	}
   132  
   133  	// If the preference file doesn't exist then we return with default preference
   134  	if _, err = os.Stat(preferenceFile); os.IsNotExist(err) {
   135  		c.OdoSettings.RegistryList = &defaultRegistryList
   136  		return &c, nil
   137  	}
   138  
   139  	err = util.GetFromFile(&c.Preference, c.Filename)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// TODO: This code block about logging warnings should be removed once users completely shift to odo v3.
   145  	// The warning will be printed more than once, and it can be annoying, but it should ensure that the user will change these values.
   146  	var requiresChange []string
   147  	if c.OdoSettings.Timeout != nil && *c.OdoSettings.Timeout < minimumDurationValue {
   148  		requiresChange = append(requiresChange, TimeoutSetting)
   149  	}
   150  	if c.OdoSettings.PushTimeout != nil && *c.OdoSettings.PushTimeout < minimumDurationValue {
   151  		requiresChange = append(requiresChange, PushTimeoutSetting)
   152  	}
   153  	if c.OdoSettings.RegistryCacheTime != nil && *c.OdoSettings.RegistryCacheTime < minimumDurationValue {
   154  		requiresChange = append(requiresChange, RegistryCacheTimeSetting)
   155  	}
   156  	if len(requiresChange) != 0 {
   157  		log.Warningf("Please change the preference value for %s, the value does not comply with the minimum value of %s; e.g. of acceptable formats: 4s, 5m, 1h", strings.Join(requiresChange, ", "), minimumDurationValue)
   158  	}
   159  
   160  	// Handle user has preference file but doesn't use dynamic registry before
   161  	if c.OdoSettings.RegistryList == nil {
   162  		c.OdoSettings.RegistryList = &defaultRegistryList
   163  	}
   164  
   165  	// Handle OCI-based default registry migration
   166  	if c.OdoSettings.RegistryList != nil {
   167  		for index, registry := range *c.OdoSettings.RegistryList {
   168  			if registry.Name == DefaultDevfileRegistryName && registry.URL == OldDefaultDevfileRegistryURL {
   169  				registryList := *c.OdoSettings.RegistryList
   170  				registryList[index].URL = DefaultDevfileRegistryURL
   171  				break
   172  			}
   173  		}
   174  	}
   175  
   176  	return &c, nil
   177  }
   178  
   179  // RegistryHandler handles registry add, and remove operations
   180  func (c *preferenceInfo) RegistryHandler(operation string, registryName string, registryURL string, forceFlag bool, isSecure bool) error {
   181  	var registryList []Registry
   182  	var err error
   183  	var registryExist bool
   184  
   185  	// Registry list is empty
   186  	if c.OdoSettings.RegistryList == nil {
   187  		registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL, isSecure)
   188  		if err != nil {
   189  			return err
   190  		}
   191  	} else {
   192  		// The target registry exists in the registry list
   193  		registryList = *c.OdoSettings.RegistryList
   194  		for index, registry := range registryList {
   195  			if registry.Name == registryName {
   196  				registryExist = true
   197  				registryList, err = handleWithRegistryExist(index, registryList, operation, registryName, forceFlag)
   198  				if err != nil {
   199  					return err
   200  				}
   201  			}
   202  		}
   203  
   204  		// The target registry doesn't exist in the registry list
   205  		if !registryExist {
   206  			registryList, err = handleWithoutRegistryExist(registryList, operation, registryName, registryURL, isSecure)
   207  			if err != nil {
   208  				return err
   209  			}
   210  		}
   211  	}
   212  
   213  	c.OdoSettings.RegistryList = &registryList
   214  	err = util.WriteToYAMLFile(&c.Preference, c.Filename)
   215  	if err != nil {
   216  		return fmt.Errorf("unable to write the configuration of %q operation to preference file", operation)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // handleWithoutRegistryExist is useful for performing 'add' operation on registry and ensure that it is only performed if the registry does not already exist
   223  func handleWithoutRegistryExist(registryList []Registry, operation string, registryName string, registryURL string, isSecure bool) ([]Registry, error) {
   224  	switch operation {
   225  
   226  	case "add":
   227  		registry := Registry{
   228  			Name:   registryName,
   229  			URL:    registryURL,
   230  			Secure: isSecure,
   231  		}
   232  		registryList = append(registryList, registry)
   233  
   234  	case "remove":
   235  		return nil, fmt.Errorf("failed to %v registry: registry %q doesn't exist or it is not managed by odo", operation, registryName)
   236  	}
   237  
   238  	return registryList, nil
   239  }
   240  
   241  // handleWithRegistryExist is useful for performing 'remove' operation on registry and ensure that it is only performed if the registry exists
   242  func handleWithRegistryExist(index int, registryList []Registry, operation string, registryName string, forceFlag bool) ([]Registry, error) {
   243  	switch operation {
   244  
   245  	case "add":
   246  		return nil, fmt.Errorf("failed to %s registry: registry %q already exists", operation, registryName)
   247  
   248  	case "remove":
   249  		if !forceFlag {
   250  			proceed, err := ui.Proceed(fmt.Sprintf("Are you sure you want to %s registry %q", operation, registryName))
   251  			if err != nil {
   252  				return nil, err
   253  			}
   254  			if !proceed {
   255  				log.Info("Aborted by the user")
   256  				return registryList, nil
   257  			}
   258  		}
   259  
   260  		copy(registryList[index:], registryList[index+1:])
   261  		registryList[len(registryList)-1] = Registry{}
   262  		registryList = registryList[:len(registryList)-1]
   263  		log.Info("Successfully removed registry")
   264  	}
   265  
   266  	return registryList, nil
   267  }
   268  
   269  // SetConfiguration modifies odo preferences in the preference file
   270  // TODO: Use reflect to set parameters
   271  func (c *preferenceInfo) SetConfiguration(parameter string, value string) error {
   272  	if p, ok := asSupportedParameter(parameter); ok {
   273  		// processing values according to the parameter names
   274  		switch p {
   275  
   276  		case "timeout":
   277  			typedval, err := parseDuration(value, parameter)
   278  			if err != nil {
   279  				return err
   280  			}
   281  			c.OdoSettings.Timeout = &typedval
   282  
   283  		case "pushtimeout":
   284  			typedval, err := parseDuration(value, parameter)
   285  			if err != nil {
   286  				return err
   287  			}
   288  			c.OdoSettings.PushTimeout = &typedval
   289  
   290  		case "registrycachetime":
   291  			typedval, err := parseDuration(value, parameter)
   292  			if err != nil {
   293  				return err
   294  			}
   295  			c.OdoSettings.RegistryCacheTime = &typedval
   296  
   297  		case "updatenotification":
   298  			val, err := strconv.ParseBool(strings.ToLower(value))
   299  			if err != nil {
   300  				return fmt.Errorf("unable to set %q to %q, value must be a boolean", parameter, value)
   301  			}
   302  			c.OdoSettings.UpdateNotification = &val
   303  
   304  		case "ephemeral":
   305  			val, err := strconv.ParseBool(strings.ToLower(value))
   306  			if err != nil {
   307  				return fmt.Errorf("unable to set %q to %q, value must be a boolean", parameter, value)
   308  			}
   309  			c.OdoSettings.Ephemeral = &val
   310  
   311  		case "consenttelemetry":
   312  			val, err := strconv.ParseBool(strings.ToLower(value))
   313  			if err != nil {
   314  				return fmt.Errorf("unable to set %q to %q, value must be a boolean", parameter, value)
   315  			}
   316  			c.OdoSettings.ConsentTelemetry = &val
   317  
   318  		case "imageregistry":
   319  			c.OdoSettings.ImageRegistry = &value
   320  		}
   321  	} else {
   322  		return fmt.Errorf("unknown parameter : %q is not a parameter in odo preference, run `odo preference -h` to see list of available parameters", parameter)
   323  	}
   324  
   325  	err := util.WriteToYAMLFile(&c.Preference, c.Filename)
   326  	if err != nil {
   327  		return fmt.Errorf("unable to set %q, something is wrong with odo, kindly raise an issue at https://github.com/redhat-developer/odo/issues/new?template=Bug.md", parameter)
   328  	}
   329  	return nil
   330  }
   331  
   332  // parseDuration parses the value set for a parameter;
   333  // if the value is for e.g. "4m", it is parsed by the time pkg and converted to an appropriate time.Duration
   334  // it returns an error if one occurred, or if the parsed value is less than minimumDurationValue
   335  func parseDuration(value, parameter string) (time.Duration, error) {
   336  	typedval, err := time.ParseDuration(value)
   337  	if err != nil {
   338  		return typedval, fmt.Errorf("unable to set %q to %q; cause: %w\n%s", parameter, value, err, NewMinimumDurationValueError().Error())
   339  	}
   340  	if typedval < minimumDurationValue {
   341  		return typedval, fmt.Errorf("unable to set %q to %q; cause: %w", parameter, value, NewMinimumDurationValueError())
   342  	}
   343  	return typedval, nil
   344  }
   345  
   346  // DeleteConfiguration deletes odo preference from the odo preference file
   347  func (c *preferenceInfo) DeleteConfiguration(parameter string) error {
   348  	if p, ok := asSupportedParameter(parameter); ok {
   349  		// processing values according to the parameter names
   350  
   351  		if err := util.DeleteConfiguration(&c.OdoSettings, p); err != nil {
   352  			return err
   353  		}
   354  	} else {
   355  		return fmt.Errorf("unknown parameter :%q is not a parameter in the odo preference", parameter)
   356  	}
   357  
   358  	err := util.WriteToYAMLFile(&c.Preference, c.Filename)
   359  	if err != nil {
   360  		return fmt.Errorf("unable to set %q, something is wrong with odo, kindly raise an issue at https://github.com/redhat-developer/odo/issues/new?template=Bug.md", parameter)
   361  	}
   362  	return nil
   363  }
   364  
   365  // IsSet checks if the value is set in the preference
   366  func (c *preferenceInfo) IsSet(parameter string) bool {
   367  	return util.IsSet(c.OdoSettings, parameter)
   368  }
   369  
   370  // GetTimeout returns the value of Timeout from config
   371  // and if absent then returns default
   372  func (c *preferenceInfo) GetTimeout() time.Duration {
   373  	// default timeout value is 1s
   374  	return kpointer.DurationDeref(c.OdoSettings.Timeout, DefaultTimeout)
   375  }
   376  
   377  // GetPushTimeout gets the value set by PushTimeout
   378  func (c *preferenceInfo) GetPushTimeout() time.Duration {
   379  	// default timeout value is 240s
   380  	return kpointer.DurationDeref(c.OdoSettings.PushTimeout, DefaultPushTimeout)
   381  }
   382  
   383  // GetRegistryCacheTime gets the value set by RegistryCacheTime
   384  func (c *preferenceInfo) GetRegistryCacheTime() time.Duration {
   385  	return kpointer.DurationDeref(c.OdoSettings.RegistryCacheTime, DefaultRegistryCacheTime)
   386  }
   387  
   388  // GetImageRegistry returns the value of ImageRegistry from the preferences
   389  // and, if absent, then returns default empty string.
   390  func (c *preferenceInfo) GetImageRegistry() string {
   391  	return kpointer.StringDeref(c.OdoSettings.ImageRegistry, "")
   392  }
   393  
   394  // GetUpdateNotification returns the value of UpdateNotification from preferences
   395  // and if absent then returns default
   396  func (c *preferenceInfo) GetUpdateNotification() bool {
   397  	return kpointer.BoolDeref(c.OdoSettings.UpdateNotification, true)
   398  }
   399  
   400  // GetEphemeralSourceVolume returns the value of ephemeral from preferences
   401  // and if absent then returns default
   402  func (c *preferenceInfo) GetEphemeralSourceVolume() bool {
   403  	return kpointer.BoolDeref(c.OdoSettings.Ephemeral, DefaultEphemeralSetting)
   404  }
   405  
   406  // GetConsentTelemetry returns the value of ConsentTelemetry from preferences
   407  // and if absent then returns default
   408  // default value: false, consent telemetry is disabled by default
   409  func (c *preferenceInfo) GetConsentTelemetry() bool {
   410  	return kpointer.BoolDeref(c.OdoSettings.ConsentTelemetry, DefaultConsentTelemetrySetting)
   411  }
   412  
   413  // GetEphemeral returns the value of Ephemeral from preferences
   414  // and if absent then returns default
   415  // default value: true, ephemeral is enabled by default
   416  func (c *preferenceInfo) GetEphemeral() bool {
   417  	return kpointer.BoolDeref(c.OdoSettings.Ephemeral, DefaultEphemeralSetting)
   418  }
   419  
   420  func (c *preferenceInfo) UpdateNotification() *bool {
   421  	return c.OdoSettings.UpdateNotification
   422  }
   423  
   424  func (c *preferenceInfo) Ephemeral() *bool {
   425  	return c.OdoSettings.Ephemeral
   426  }
   427  
   428  func (c *preferenceInfo) Timeout() *time.Duration {
   429  	return c.OdoSettings.Timeout
   430  }
   431  
   432  func (c *preferenceInfo) PushTimeout() *time.Duration {
   433  	return c.OdoSettings.PushTimeout
   434  }
   435  
   436  func (c *preferenceInfo) RegistryCacheTime() *time.Duration {
   437  	return c.OdoSettings.RegistryCacheTime
   438  }
   439  
   440  func (c *preferenceInfo) EphemeralSourceVolume() *bool {
   441  	return c.OdoSettings.Ephemeral
   442  }
   443  
   444  func (c *preferenceInfo) ConsentTelemetry() *bool {
   445  	return c.OdoSettings.ConsentTelemetry
   446  }
   447  
   448  // RegistryList returns the list of registries,
   449  // in reverse order compared to what is declared in the preferences file.
   450  //
   451  // Adding a new registry always adds it to the end of the list in the preferences file,
   452  // but RegistryList intentionally reverses the order to prioritize the most recently added registries.
   453  func (c *preferenceInfo) RegistryList() []api.Registry {
   454  	if c.OdoSettings.RegistryList == nil {
   455  		return nil
   456  	}
   457  	regList := make([]api.Registry, 0, len(*c.OdoSettings.RegistryList))
   458  	for _, registry := range *c.OdoSettings.RegistryList {
   459  		regList = append(regList, api.Registry{
   460  			Name:   registry.Name,
   461  			URL:    registry.URL,
   462  			Secure: registry.Secure,
   463  		})
   464  	}
   465  	i := 0
   466  	j := len(regList) - 1
   467  	for i < j {
   468  		regList[i], regList[j] = regList[j], regList[i]
   469  		i++
   470  		j--
   471  	}
   472  	return regList
   473  }
   474  
   475  func (c *preferenceInfo) RegistryNameExists(name string) bool {
   476  	for _, registry := range *c.OdoSettings.RegistryList {
   477  		if registry.Name == name {
   478  			return true
   479  		}
   480  	}
   481  	return false
   482  }
   483  
   484  // FormatSupportedParameters outputs supported parameters and their description
   485  func FormatSupportedParameters() (result string) {
   486  	for _, v := range GetSupportedParameters() {
   487  		result = result + " " + v + " - " + supportedParameterDescriptions[v] + "\n"
   488  	}
   489  	return "\nAvailable Global Parameters:\n" + result
   490  }
   491  
   492  // asSupportedParameter checks that the given parameter is supported and returns a lower case version of it if it is
   493  func asSupportedParameter(param string) (string, bool) {
   494  	lower := strings.ToLower(param)
   495  	return lower, lowerCaseParameters[lower]
   496  }
   497  
   498  // GetSupportedParameters returns the name of the supported parameters
   499  func GetSupportedParameters() []string {
   500  	return dfutil.GetSortedKeys(supportedParameterDescriptions)
   501  }
   502  

View as plain text