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 }