github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/util/configv3/config.go (about)

     1  // Package configv3 package contains everything related to the CF CLI Configuration.
     2  package configv3
     3  
     4  import (
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"golang.org/x/crypto/ssh/terminal"
    15  
    16  	"code.cloudfoundry.org/cli/version"
    17  )
    18  
    19  const (
    20  	// DefaultDialTimeout is the default timeout for the dail.
    21  	DefaultDialTimeout = 5 * time.Second
    22  
    23  	// DefaultOverallPollingTimeout is the default maximum time that the CLI will
    24  	// poll a job running on the Cloud Controller. By default it's infinit, which
    25  	// is represented by MaxInt64.
    26  	DefaultOverallPollingTimeout = time.Duration(1 << 62)
    27  	// Developer Note: Due to bugs in using MaxInt64 during comparison, the above
    28  	// was chosen as a replacement.
    29  
    30  	// DefaultPollingInterval is the time between consecutive polls of a status.
    31  	DefaultPollingInterval = 3 * time.Second
    32  
    33  	// DefaultStagingTimeout is the default timeout for application staging.
    34  	DefaultStagingTimeout = 15 * time.Minute
    35  
    36  	// DefaultStartupTimeout is the default timeout for application starting.
    37  	DefaultStartupTimeout = 5 * time.Minute
    38  	// DefaultPingerThrottle = 5 * time.Second
    39  
    40  	// DefaultTarget is the default CFConfig value for Target.
    41  	DefaultTarget = ""
    42  
    43  	// DefaultUAAOAuthClient is the default client ID for the CLI when
    44  	// communicating with the UAA.
    45  	DefaultUAAOAuthClient = "cf"
    46  
    47  	// DefaultCFOClientSecret is the default client secret for the CLI when
    48  	// communicating with the UAA.
    49  	DefaultUAAOAuthClientSecret = ""
    50  )
    51  
    52  // LoadConfig loads the config from the .cf/config.json and os.ENV. If the
    53  // config.json does not exists, it will use a default config in it's place.
    54  // Takes in an optional FlagOverride, will only use the first one passed, that
    55  // can override the given flag values.
    56  //
    57  // The '.cf' directory will be read in one of the following locations on UNIX
    58  // Systems:
    59  //   1. $CF_HOME/.cf if $CF_HOME is set
    60  //   2. $HOME/.cf as the default
    61  //
    62  // The '.cf' directory will be read in one of the following locations on
    63  // Windows Systems:
    64  //   1. CF_HOME\.cf if CF_HOME is set
    65  //   2. HOMEDRIVE\HOMEPATH\.cf if HOMEDRIVE or HOMEPATH is set
    66  //   3. USERPROFILE\.cf as the default
    67  func LoadConfig(flags ...FlagOverride) (*Config, error) {
    68  	filePath := ConfigFilePath()
    69  
    70  	var config Config
    71  	if _, err := os.Stat(filePath); os.IsNotExist(err) {
    72  		config = Config{
    73  			ConfigFile: CFConfig{
    74  				ConfigVersion: 3,
    75  				Target:        DefaultTarget,
    76  				ColorEnabled:  DefaultColorEnabled,
    77  				PluginRepositories: []PluginRepository{{
    78  					Name: DefaultPluginRepoName,
    79  					URL:  DefaultPluginRepoURL,
    80  				}},
    81  				UAAOAuthClient:       DefaultUAAOAuthClient,
    82  				UAAOAuthClientSecret: DefaultUAAOAuthClientSecret,
    83  			},
    84  		}
    85  	} else {
    86  		file, err := ioutil.ReadFile(filePath)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  
    91  		err = json.Unmarshal(file, &config.ConfigFile)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  
    96  		if config.ConfigFile.UAAOAuthClient == "" {
    97  			config.ConfigFile.UAAOAuthClient = DefaultUAAOAuthClient
    98  			config.ConfigFile.UAAOAuthClientSecret = DefaultUAAOAuthClientSecret
    99  		}
   100  	}
   101  
   102  	config.ENV = EnvOverride{
   103  		BinaryName:       filepath.Base(os.Args[0]),
   104  		CFColor:          os.Getenv("CF_COLOR"),
   105  		CFPluginHome:     os.Getenv("CF_PLUGIN_HOME"),
   106  		CFStagingTimeout: os.Getenv("CF_STAGING_TIMEOUT"),
   107  		CFStartupTimeout: os.Getenv("CF_STARTUP_TIMEOUT"),
   108  		CFTrace:          os.Getenv("CF_TRACE"),
   109  		HTTPSProxy:       os.Getenv("https_proxy"),
   110  		Lang:             os.Getenv("LANG"),
   111  		LCAll:            os.Getenv("LC_ALL"),
   112  		Experimental:     os.Getenv("CF_CLI_EXPERIMENTAL"),
   113  		CFDialTimeout:    os.Getenv("CF_DIAL_TIMEOUT"),
   114  		ForceTTY:         os.Getenv("FORCE_TTY"),
   115  		CFLogLevel:       os.Getenv("CF_LOG_LEVEL"),
   116  	}
   117  
   118  	pluginFilePath := filepath.Join(config.PluginHome(), "config.json")
   119  	if _, err := os.Stat(pluginFilePath); os.IsNotExist(err) {
   120  		config.pluginsConfig = PluginsConfig{
   121  			Plugins: make(map[string]Plugin),
   122  		}
   123  	} else {
   124  		file, err := ioutil.ReadFile(pluginFilePath)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  
   129  		err = json.Unmarshal(file, &config.pluginsConfig)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  
   134  		for name, plugin := range config.pluginsConfig.Plugins {
   135  			plugin.Name = name
   136  			config.pluginsConfig.Plugins[name] = plugin
   137  		}
   138  	}
   139  
   140  	if len(flags) > 0 {
   141  		config.Flags = flags[0]
   142  	}
   143  
   144  	// Developer Note: The following is untested! Change at your own risk.
   145  	isTTY := terminal.IsTerminal(int(os.Stdout.Fd()))
   146  	terminalWidth := math.MaxInt32
   147  
   148  	if isTTY {
   149  		var err error
   150  		terminalWidth, _, err = terminal.GetSize(int(os.Stdout.Fd()))
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  	}
   155  
   156  	config.detectedSettings = detectedSettings{
   157  		tty:           isTTY,
   158  		terminalWidth: terminalWidth,
   159  	}
   160  
   161  	return &config, nil
   162  }
   163  
   164  // WriteConfig creates the .cf directory and then writes the config.json. The
   165  // location of .cf directory is written in the same way LoadConfig reads .cf
   166  // directory.
   167  func WriteConfig(c *Config) error {
   168  	rawConfig, err := json.MarshalIndent(c.ConfigFile, "", "  ")
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	err = os.MkdirAll(filepath.Join(homeDirectory(), ".cf"), 0700)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	return ioutil.WriteFile(ConfigFilePath(), rawConfig, 0600)
   179  }
   180  
   181  // Config combines the settings taken from the .cf/config.json, os.ENV, and the
   182  // plugin config.
   183  type Config struct {
   184  	// ConfigFile stores the configuration from the .cf/config
   185  	ConfigFile CFConfig
   186  
   187  	// ENV stores the configuration from os.ENV
   188  	ENV EnvOverride
   189  
   190  	// Flags stores the configuration from gobal flags
   191  	Flags FlagOverride
   192  
   193  	// detectedSettings are settings detected when the config is loaded.
   194  	detectedSettings detectedSettings
   195  
   196  	pluginsConfig PluginsConfig
   197  }
   198  
   199  // CFConfig represents .cf/config.json
   200  type CFConfig struct {
   201  	ConfigVersion            int                `json:"ConfigVersion"`
   202  	Target                   string             `json:"Target"`
   203  	APIVersion               string             `json:"APIVersion"`
   204  	AuthorizationEndpoint    string             `json:"AuthorizationEndpoint"`
   205  	DopplerEndpoint          string             `json:"DopplerEndPoint"`
   206  	UAAEndpoint              string             `json:"UaaEndpoint"`
   207  	RoutingEndpoint          string             `json:"RoutingAPIEndpoint"`
   208  	AccessToken              string             `json:"AccessToken"`
   209  	SSHOAuthClient           string             `json:"SSHOAuthClient"`
   210  	UAAOAuthClient           string             `json:"UAAOAuthClient"`
   211  	UAAOAuthClientSecret     string             `json:"UAAOAuthClientSecret"`
   212  	RefreshToken             string             `json:"RefreshToken"`
   213  	TargetedOrganization     Organization       `json:"OrganizationFields"`
   214  	TargetedSpace            Space              `json:"SpaceFields"`
   215  	SkipSSLValidation        bool               `json:"SSLDisabled"`
   216  	AsyncTimeout             int                `json:"AsyncTimeout"`
   217  	Trace                    string             `json:"Trace"`
   218  	ColorEnabled             string             `json:"ColorEnabled"`
   219  	Locale                   string             `json:"Locale"`
   220  	PluginRepositories       []PluginRepository `json:"PluginRepos"`
   221  	MinCLIVersion            string             `json:"MinCLIVersion"`
   222  	MinRecommendedCLIVersion string             `json:"MinRecommendedCLIVersion"`
   223  }
   224  
   225  // Organization contains basic information about the targeted organization
   226  type Organization struct {
   227  	GUID            string          `json:"GUID"`
   228  	Name            string          `json:"Name"`
   229  	QuotaDefinition QuotaDefinition `json:"QuotaDefinition"`
   230  }
   231  
   232  // QuotaDefinition contains information about the organization's quota
   233  type QuotaDefinition struct {
   234  	GUID                    string `json:"guid"`
   235  	Name                    string `json:"name"`
   236  	MemoryLimit             int    `json:"memory_limit"`
   237  	InstanceMemoryLimit     int    `json:"instance_memory_limit"`
   238  	TotalRoutes             int    `json:"total_routes"`
   239  	TotalServices           int    `json:"total_services"`
   240  	NonBasicServicesAllowed bool   `json:"non_basic_services_allowed"`
   241  	AppInstanceLimit        int    `json:"app_instance_limit"`
   242  	TotalReservedRoutePorts int    `json:"total_reserved_route_ports"`
   243  }
   244  
   245  // Space contains basic information about the targeted space
   246  type Space struct {
   247  	GUID     string `json:"GUID"`
   248  	Name     string `json:"Name"`
   249  	AllowSSH bool   `json:"AllowSSH"`
   250  }
   251  
   252  // EnvOverride represents all the environment variables read by the CF CLI
   253  type EnvOverride struct {
   254  	BinaryName       string
   255  	CFColor          string
   256  	CFHome           string
   257  	CFPluginHome     string
   258  	CFStagingTimeout string
   259  	CFStartupTimeout string
   260  	CFTrace          string
   261  	HTTPSProxy       string
   262  	Lang             string
   263  	LCAll            string
   264  	Experimental     string
   265  	CFDialTimeout    string
   266  	ForceTTY         string
   267  	CFLogLevel       string
   268  }
   269  
   270  // FlagOverride represents all the global flags passed to the CF CLI
   271  type FlagOverride struct {
   272  	Verbose bool
   273  }
   274  
   275  // detectedSettings are automatically detected settings determined by the CLI.
   276  type detectedSettings struct {
   277  	tty           bool
   278  	terminalWidth int
   279  }
   280  
   281  // Target returns the CC API URL
   282  func (config *Config) Target() string {
   283  	return config.ConfigFile.Target
   284  }
   285  
   286  // PollingInterval returns the time between polls.
   287  func (config *Config) PollingInterval() time.Duration {
   288  	return DefaultPollingInterval
   289  }
   290  
   291  // OverallPollingTimeout returns the overall polling timeout for async
   292  // operations. The time is based off of:
   293  //   1. The config file's AsyncTimeout value (integer) is > 0
   294  //   2. Defaults to the DefaultOverallPollingTimeout
   295  func (config *Config) OverallPollingTimeout() time.Duration {
   296  	if config.ConfigFile.AsyncTimeout == 0 {
   297  		return DefaultOverallPollingTimeout
   298  	}
   299  	return time.Duration(config.ConfigFile.AsyncTimeout) * time.Minute
   300  }
   301  
   302  // SkipSSLValidation returns whether or not to skip SSL validation when
   303  // targeting an API endpoint
   304  func (config *Config) SkipSSLValidation() bool {
   305  	return config.ConfigFile.SkipSSLValidation
   306  }
   307  
   308  // AccessToken returns the access token for making authenticated API calls
   309  func (config *Config) AccessToken() string {
   310  	return config.ConfigFile.AccessToken
   311  }
   312  
   313  // RefreshToken returns the refresh token for getting a new access token
   314  func (config *Config) RefreshToken() string {
   315  	return config.ConfigFile.RefreshToken
   316  }
   317  
   318  // UAAOAuthClient returns the CLI's UAA client ID
   319  func (config *Config) UAAOAuthClient() string {
   320  	return config.ConfigFile.UAAOAuthClient
   321  }
   322  
   323  // UAAOAuthClientSecret returns the CLI's UAA client secret
   324  func (config *Config) UAAOAuthClientSecret() string {
   325  	return config.ConfigFile.UAAOAuthClientSecret
   326  }
   327  
   328  // APIVersion returns the CC API Version
   329  func (config *Config) APIVersion() string {
   330  	return config.ConfigFile.APIVersion
   331  }
   332  
   333  // MinCLIVersion returns the minimum CLI version requried by the CC
   334  func (config *Config) MinCLIVersion() string {
   335  	return config.ConfigFile.MinCLIVersion
   336  }
   337  
   338  // TargetedOrganization returns the currently targeted organization
   339  func (config *Config) TargetedOrganization() Organization {
   340  	return config.ConfigFile.TargetedOrganization
   341  }
   342  
   343  // TargetedSpace returns the currently targeted space
   344  func (config *Config) TargetedSpace() Space {
   345  	return config.ConfigFile.TargetedSpace
   346  }
   347  
   348  // StagingTimeout returns the max time an application staging should take. The
   349  // time is based off of:
   350  //   1. The $CF_STAGING_TIMEOUT environment variable if set
   351  //   2. Defaults to the DefaultStagingTimeout
   352  func (config *Config) StagingTimeout() time.Duration {
   353  	if config.ENV.CFStagingTimeout != "" {
   354  		val, err := strconv.ParseInt(config.ENV.CFStagingTimeout, 10, 64)
   355  		if err == nil {
   356  			return time.Duration(val) * time.Minute
   357  		}
   358  	}
   359  
   360  	return DefaultStagingTimeout
   361  }
   362  
   363  // StartupTimeout returns the max time an application should take to start. The
   364  // time is based off of:
   365  //   1. The $CF_STARTUP_TIMEOUT environment variable if set
   366  //   2. Defaults to the DefaultStartupTimeout
   367  func (config *Config) StartupTimeout() time.Duration {
   368  	if config.ENV.CFStartupTimeout != "" {
   369  		val, err := strconv.ParseInt(config.ENV.CFStartupTimeout, 10, 64)
   370  		if err == nil {
   371  			return time.Duration(val) * time.Minute
   372  		}
   373  	}
   374  
   375  	return DefaultStartupTimeout
   376  }
   377  
   378  // HTTPSProxy returns the proxy url that the CLI should use. The url is based
   379  // off of:
   380  //   1. The $https_proxy environment variable if set
   381  //   2. Defaults to the empty string
   382  func (config *Config) HTTPSProxy() string {
   383  	if config.ENV.HTTPSProxy != "" {
   384  		return config.ENV.HTTPSProxy
   385  	}
   386  
   387  	return ""
   388  }
   389  
   390  // BinaryName returns the running name of the CF CLI
   391  func (config *Config) BinaryName() string {
   392  	return config.ENV.BinaryName
   393  }
   394  
   395  // Experimental returns whether or not to run experimental CLI commands. This
   396  // is based off of:
   397  //   1. The $CF_CLI_EXPERIMENTAL environment variable if set
   398  //   2. Defaults to false
   399  func (config *Config) Experimental() bool {
   400  	if config.ENV.Experimental != "" {
   401  		envVal, err := strconv.ParseBool(config.ENV.Experimental)
   402  		if err == nil {
   403  			return envVal
   404  		}
   405  	}
   406  
   407  	return false
   408  }
   409  
   410  // Verbose returns true if verbose should be displayed to terminal and a
   411  // location to log to. This is based off of:
   412  //   - The config file's trace value (true/false/file path)
   413  //   - The $CF_TRACE enviroment variable if set (true/false/file path)
   414  //   - The '-v/--verbose' global flag
   415  //   - Defaults to false
   416  func (config *Config) Verbose() (bool, []string) {
   417  	var (
   418  		verbose     bool
   419  		envOverride bool
   420  		filePath    []string
   421  	)
   422  	if config.ENV.CFTrace != "" {
   423  		envVal, err := strconv.ParseBool(config.ENV.CFTrace)
   424  		verbose = envVal
   425  		if err != nil {
   426  			filePath = []string{config.ENV.CFTrace}
   427  		} else {
   428  			envOverride = true
   429  		}
   430  	}
   431  	if config.ConfigFile.Trace != "" {
   432  		envVal, err := strconv.ParseBool(config.ConfigFile.Trace)
   433  		if !envOverride {
   434  			verbose = envVal || verbose
   435  		}
   436  		if err != nil {
   437  			filePath = append(filePath, config.ConfigFile.Trace)
   438  		}
   439  	}
   440  	verbose = config.Flags.Verbose || verbose
   441  
   442  	return verbose, filePath
   443  }
   444  
   445  // IsTTY returns true based off of:
   446  //   - The $FORCE_TTY is set to true/t/1
   447  //   - Detected from the STDOUT stream
   448  func (config *Config) IsTTY() bool {
   449  	if config.ENV.ForceTTY != "" {
   450  		envVal, err := strconv.ParseBool(config.ENV.ForceTTY)
   451  		if err == nil {
   452  			return envVal
   453  		}
   454  	}
   455  
   456  	return config.detectedSettings.tty
   457  }
   458  
   459  // LogLevel returns the global log level. The levels follow Logrus's log level
   460  // scheme. This value is based off of:
   461  //   - The $CF_LOG_LEVEL and an int/warn/info/etc...
   462  //   - Defaults to PANIC/0 (ie no logging)
   463  func (config *Config) LogLevel() int {
   464  	if config.ENV.CFLogLevel != "" {
   465  		envVal, err := strconv.ParseInt(config.ENV.CFLogLevel, 10, 32)
   466  		if err == nil {
   467  			return int(envVal)
   468  		}
   469  
   470  		switch strings.ToLower(config.ENV.CFLogLevel) {
   471  		case "fatal":
   472  			return 1
   473  		case "error":
   474  			return 2
   475  		case "warn":
   476  			return 3
   477  		case "info":
   478  			return 4
   479  		case "debug":
   480  			return 5
   481  		}
   482  	}
   483  
   484  	return 0
   485  }
   486  
   487  // TerminalWidth returns the width of the terminal from when the config
   488  // was loaded. If the terminal width has changed since the config has loaded,
   489  // it will **not** return the new width.
   490  func (config *Config) TerminalWidth() int {
   491  	return config.detectedSettings.terminalWidth
   492  }
   493  
   494  // DialTimeout returns the timeout to use when dialing. This is based off of:
   495  //   1. The $CF_DIAL_TIMEOUT environment variable if set
   496  //   2. Defaults to 5 seconds
   497  func (config *Config) DialTimeout() time.Duration {
   498  	if config.ENV.CFDialTimeout != "" {
   499  		envVal, err := strconv.ParseInt(config.ENV.CFDialTimeout, 10, 64)
   500  		if err == nil {
   501  			return time.Duration(envVal) * time.Second
   502  		}
   503  	}
   504  
   505  	return DefaultDialTimeout
   506  }
   507  
   508  func (config *Config) BinaryVersion() string {
   509  	return version.VersionString()
   510  }
   511  
   512  // HasTargetedOrganization returns true if the organization is set
   513  func (config *Config) HasTargetedOrganization() bool {
   514  	return config.ConfigFile.TargetedOrganization.GUID != ""
   515  }
   516  
   517  // HasTargetedSpace returns true if the space is set
   518  func (config *Config) HasTargetedSpace() bool {
   519  	return config.ConfigFile.TargetedSpace.GUID != ""
   520  }
   521  
   522  // SetOrganizationInformation sets the currently targeted organization
   523  func (config *Config) SetOrganizationInformation(guid string, name string) {
   524  	config.ConfigFile.TargetedOrganization.GUID = guid
   525  	config.ConfigFile.TargetedOrganization.Name = name
   526  	config.ConfigFile.TargetedOrganization.QuotaDefinition = QuotaDefinition{}
   527  }
   528  
   529  // SetSpaceInformation sets the currently targeted space
   530  func (config *Config) SetSpaceInformation(guid string, name string, allowSSH bool) {
   531  	config.ConfigFile.TargetedSpace.GUID = guid
   532  	config.ConfigFile.TargetedSpace.Name = name
   533  	config.ConfigFile.TargetedSpace.AllowSSH = allowSSH
   534  }
   535  
   536  // SetTargetInformation sets the currently targeted CC API and related other
   537  // related API URLs
   538  func (config *Config) SetTargetInformation(api string, apiVersion string, auth string, minCLIVersion string, doppler string, uaa string, routing string, skipSSLValidation bool) {
   539  	config.ConfigFile.Target = api
   540  	config.ConfigFile.APIVersion = apiVersion
   541  	config.ConfigFile.AuthorizationEndpoint = auth
   542  	config.ConfigFile.MinCLIVersion = minCLIVersion
   543  	config.ConfigFile.DopplerEndpoint = doppler
   544  	config.ConfigFile.UAAEndpoint = uaa
   545  	config.ConfigFile.RoutingEndpoint = routing
   546  	config.ConfigFile.SkipSSLValidation = skipSSLValidation
   547  
   548  	config.UnsetOrganizationInformation()
   549  	config.UnsetSpaceInformation()
   550  }
   551  
   552  // SetTokenInformation sets the current token/user information
   553  func (config *Config) SetTokenInformation(accessToken string, refreshToken string, sshOAuthClient string) {
   554  	config.ConfigFile.AccessToken = accessToken
   555  	config.ConfigFile.RefreshToken = refreshToken
   556  	config.ConfigFile.SSHOAuthClient = sshOAuthClient
   557  }
   558  
   559  // SetAccessToken sets the current access token
   560  func (config *Config) SetAccessToken(accessToken string) {
   561  	config.ConfigFile.AccessToken = accessToken
   562  }
   563  
   564  // SetRefreshToken sets the current refresh token
   565  func (config *Config) SetRefreshToken(refreshToken string) {
   566  	config.ConfigFile.RefreshToken = refreshToken
   567  }
   568  
   569  // UnsetSpaceInformation resets the space values to default
   570  func (config *Config) UnsetSpaceInformation() {
   571  	config.SetSpaceInformation("", "", false)
   572  }
   573  
   574  // UnsetOrganizationInformation resets the organization values to default
   575  func (config *Config) UnsetOrganizationInformation() {
   576  	config.SetOrganizationInformation("", "")
   577  }