packer-cn/vendor/github.com/scaleway/scaleway-sdk-go/scw/config.go

344 lines
12 KiB
Go
Raw Normal View History

package scw
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
"gopkg.in/yaml.v2"
)
const (
documentationLink = "https://github.com/scaleway/scaleway-sdk-go/blob/master/scw/README.md"
defaultConfigPermission = 0600
// Reserved name for the default profile.
DefaultProfileName = "default"
)
const configFileTemplate = `# Scaleway configuration file
# https://github.com/scaleway/scaleway-sdk-go/tree/master/scw#scaleway-config
# This configuration file can be used with:
# - Scaleway SDK Go (https://github.com/scaleway/scaleway-sdk-go)
# - Scaleway CLI (>2.0.0) (https://github.com/scaleway/scaleway-cli)
# - Scaleway Terraform Provider (https://www.terraform.io/docs/providers/scaleway/index.html)
# You need an access key and a secret key to connect to Scaleway API.
# Generate your token at the following address: https://console.scaleway.com/account/credentials
# An access key is a secret key identifier.
{{ if .AccessKey }}access_key: {{.AccessKey}}{{ else }}# access_key: SCW11111111111111111{{ end }}
# The secret key is the value that can be used to authenticate against the API (the value used in X-Auth-Token HTTP-header).
# The secret key MUST remain secret and not given to anyone or published online.
{{ if .SecretKey }}secret_key: {{ .SecretKey }}{{ else }}# secret_key: 11111111-1111-1111-1111-111111111111{{ end }}
# Your organization ID is the identifier of your account inside Scaleway infrastructure.
{{ if .DefaultOrganizationID }}default_organization_id: {{ .DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }}
# Your project ID is the identifier of the project your resources are attached to (beta).
{{ if .DefaultProjectID }}default_project_id: {{ .DefaultProjectID }}{{ else }}# default_project_id: 11111111-1111-1111-1111-111111111111{{ end }}
# A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam).
# It can contain multiple availability zones.
# Example of region: fr-par, nl-ams
{{ if .DefaultRegion }}default_region: {{ .DefaultRegion }}{{ else }}# default_region: fr-par{{ end }}
# A region can be split into many availability zones (AZ).
# Latency between multiple AZ of the same region are low as they have a common network layer.
# Example of zones: fr-par-1, nl-ams-1
{{ if .DefaultZone }}default_zone: {{.DefaultZone}}{{ else }}# default_zone: fr-par-1{{ end }}
# APIURL overrides the API URL of the Scaleway API to the given URL.
# Change that if you want to direct requests to a different endpoint.
{{ if .APIURL }}apiurl: {{ .APIURL }}{{ else }}# api_url: https://api.scaleway.com{{ end }}
# Insecure enables insecure transport on the client.
# Default to false
{{ if .Insecure }}insecure: {{ .Insecure }}{{ else }}# insecure: false{{ end }}
# A configuration is a named set of Scaleway properties.
# Starting off with a Scaleway SDK or Scaleway CLI, youll work with a single configuration named default.
# You can set properties of the default profile by running either scw init or scw config set.
# This single default configuration is suitable for most use cases.
{{ if .ActiveProfile }}active_profile: {{ .ActiveProfile }}{{ else }}# active_profile: myProfile{{ end }}
# To improve the Scaleway CLI we rely on diagnostic and usage data.
# Sending such data is optional and can be disable at any time by setting send_telemetry variable to false.
{{ if .SendTelemetry }}send_telemetry: {{ .SendTelemetry }}{{ else }}# send_telemetry: false{{ end }}
# To work with multiple projects or authorization accounts, you can set up multiple configurations with scw config configurations create and switch among them accordingly.
# You can use a profile by either:
# - Define the profile you want to use as the SCW_PROFILE environment variable
# - Use the GetActiveProfile() function in the SDK
# - Use the --profile flag with the CLI
# You can define a profile using the following syntax:
{{ if gt (len .Profiles) 0 }}
profiles:
{{- range $k,$v := .Profiles }}
{{ $k }}:
{{ if $v.AccessKey }}access_key: {{ $v.AccessKey }}{{ else }}# access_key: SCW11111111111111111{{ end }}
{{ if $v.SecretKey }}secret_key: {{ $v.SecretKey }}{{ else }}# secret_key: 11111111-1111-1111-1111-111111111111{{ end }}
{{ if $v.DefaultOrganizationID }}default_organization_id: {{ $v.DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }}
{{ if $v.DefaultProjectID }}default_project_id: {{ $v.DefaultProjectID }}{{ else }}# default_project_id: 11111111-1111-1111-1111-111111111111{{ end }}
{{ if $v.DefaultZone }}default_zone: {{ $v.DefaultZone }}{{ else }}# default_zone: fr-par-1{{ end }}
{{ if $v.DefaultRegion }}default_region: {{ $v.DefaultRegion }}{{ else }}# default_region: fr-par{{ end }}
{{ if $v.APIURL }}api_url: {{ $v.APIURL }}{{ else }}# api_url: https://api.scaleway.com{{ end }}
{{ if $v.Insecure }}insecure: {{ $v.Insecure }}{{ else }}# insecure: false{{ end }}
{{ end }}
{{- else }}
# profiles:
# myProfile:
# access_key: 11111111-1111-1111-1111-111111111111
# secret_key: 11111111-1111-1111-1111-111111111111
# default_organization_id: 11111111-1111-1111-1111-111111111111
# default_project_id: 11111111-1111-1111-1111-111111111111
# default_zone: fr-par-1
# default_region: fr-par
# api_url: https://api.scaleway.com
# insecure: false
{{ end -}}
`
type Config struct {
Profile `yaml:",inline"`
ActiveProfile *string `yaml:"active_profile,omitempty" json:"active_profile,omitempty"`
Profiles map[string]*Profile `yaml:"profiles,omitempty" json:"profiles,omitempty"`
}
type Profile struct {
AccessKey *string `yaml:"access_key,omitempty" json:"access_key,omitempty"`
SecretKey *string `yaml:"secret_key,omitempty" json:"secret_key,omitempty"`
APIURL *string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure,omitempty"`
DefaultOrganizationID *string `yaml:"default_organization_id,omitempty" json:"default_organization_id,omitempty"`
DefaultProjectID *string `yaml:"default_project_id,omitempty" json:"default_project_id,omitempty"`
DefaultRegion *string `yaml:"default_region,omitempty" json:"default_region,omitempty"`
DefaultZone *string `yaml:"default_zone,omitempty" json:"default_zone,omitempty"`
SendTelemetry *bool `yaml:"send_telemetry,omitempty" json:"send_telemetry,omitempty"`
}
func (p *Profile) String() string {
p2 := *p
p2.SecretKey = hideSecretKey(p2.SecretKey)
configRaw, _ := yaml.Marshal(p2)
return string(configRaw)
}
// clone deep copy config object
func (c *Config) clone() *Config {
c2 := &Config{}
configRaw, _ := yaml.Marshal(c)
_ = yaml.Unmarshal(configRaw, c2)
return c2
}
func (c *Config) String() string {
c2 := c.clone()
c2.SecretKey = hideSecretKey(c2.SecretKey)
for _, p := range c2.Profiles {
p.SecretKey = hideSecretKey(p.SecretKey)
}
configRaw, _ := yaml.Marshal(c2)
return string(configRaw)
}
func (c *Config) IsEmpty() bool {
return c.String() == "{}\n"
}
func hideSecretKey(key *string) *string {
if key == nil {
return nil
}
newKey := auth.HideSecretKey(*key)
return &newKey
}
func unmarshalConfV2(content []byte) (*Config, error) {
var config Config
err := yaml.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, nil
}
// MustLoadConfig is like LoadConfig but panic instead of returning an error.
func MustLoadConfig() *Config {
c, err := LoadConfigFromPath(GetConfigPath())
if err != nil {
panic(err)
}
return c
}
// LoadConfig read the config from the default path.
func LoadConfig() (*Config, error) {
return LoadConfigFromPath(GetConfigPath())
}
// LoadConfigFromPath read the config from the given path.
func LoadConfigFromPath(path string) (*Config, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return nil, configFileNotFound(path)
}
if err != nil {
return nil, err
}
file, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrap(err, "cannot read config file")
}
_, err = unmarshalConfV1(file)
if err == nil {
// reject V1 config
return nil, errors.New("found legacy config in %s: legacy config is not allowed, please switch to the new config file format: %s", path, documentationLink)
}
confV2, err := unmarshalConfV2(file)
if err != nil {
return nil, errors.Wrap(err, "content of config file %s is invalid", path)
}
return confV2, nil
}
// GetProfile returns the profile corresponding to the given profile name.
func (c *Config) GetProfile(profileName string) (*Profile, error) {
if profileName == "" {
return nil, errors.New("profileName cannot be empty")
}
if profileName == DefaultProfileName {
return &c.Profile, nil
}
p, exist := c.Profiles[profileName]
if !exist {
return nil, errors.New("given profile %s does not exist", profileName)
}
// Merge selected profile on top of default profile
return MergeProfiles(&c.Profile, p), nil
}
// GetActiveProfile returns the active profile of the config based on the following order:
// env SCW_PROFILE > config active_profile > config root profile
func (c *Config) GetActiveProfile() (*Profile, error) {
switch {
case os.Getenv(ScwActiveProfileEnv) != "":
logger.Debugf("using active profile from env: %s=%s", ScwActiveProfileEnv, os.Getenv(ScwActiveProfileEnv))
return c.GetProfile(os.Getenv(ScwActiveProfileEnv))
case c.ActiveProfile != nil:
logger.Debugf("using active profile from config: active_profile=%s", ScwActiveProfileEnv, *c.ActiveProfile)
return c.GetProfile(*c.ActiveProfile)
default:
return &c.Profile, nil
}
}
// SaveTo will save the config to the default config path. This
// action will overwrite the previous file when it exists.
func (c *Config) Save() error {
return c.SaveTo(GetConfigPath())
}
// HumanConfig will generate a config file with documented arguments.
func (c *Config) HumanConfig() (string, error) {
tmpl, err := template.New("configuration").Parse(configFileTemplate)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, c)
if err != nil {
return "", err
}
return buf.String(), nil
}
// SaveTo will save the config to the given path. This action will
// overwrite the previous file when it exists.
func (c *Config) SaveTo(path string) error {
path = filepath.Clean(path)
// STEP 1: Render the configuration file as a file
file, err := c.HumanConfig()
if err != nil {
return err
}
// STEP 2: create config path dir in cases it didn't exist before
err = os.MkdirAll(filepath.Dir(path), 0700)
if err != nil {
return err
}
// STEP 3: write new config file
err = ioutil.WriteFile(path, []byte(file), defaultConfigPermission)
if err != nil {
return err
}
return nil
}
// MergeProfiles merges profiles in a new one. The last profile has priority.
func MergeProfiles(original *Profile, others ...*Profile) *Profile {
np := &Profile{
AccessKey: original.AccessKey,
SecretKey: original.SecretKey,
APIURL: original.APIURL,
Insecure: original.Insecure,
DefaultOrganizationID: original.DefaultOrganizationID,
DefaultProjectID: original.DefaultProjectID,
DefaultRegion: original.DefaultRegion,
DefaultZone: original.DefaultZone,
}
for _, other := range others {
if other.AccessKey != nil {
np.AccessKey = other.AccessKey
}
if other.SecretKey != nil {
np.SecretKey = other.SecretKey
}
if other.APIURL != nil {
np.APIURL = other.APIURL
}
if other.Insecure != nil {
np.Insecure = other.Insecure
}
if other.DefaultOrganizationID != nil {
np.DefaultOrganizationID = other.DefaultOrganizationID
}
if other.DefaultProjectID != nil {
np.DefaultProjectID = other.DefaultProjectID
}
if other.DefaultRegion != nil {
np.DefaultRegion = other.DefaultRegion
}
if other.DefaultZone != nil {
np.DefaultZone = other.DefaultZone
}
}
return np
}