github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/joyent/config.go (about)

     1  // Copyright 2013 Joyent Inc.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package joyent
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/url"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/schema"
    15  	"github.com/juju/utils"
    16  
    17  	"github.com/juju/juju/environs/config"
    18  )
    19  
    20  // boilerplateConfig will be shown in help output, so please keep it up to
    21  // date when you change environment configuration below.
    22  const boilerplateConfig = `joyent:
    23    type: joyent
    24  
    25    # SDC config
    26    # Can be set via env variables, or specified here
    27    # sdc-user: <secret>
    28    # Can be set via env variables, or specified here
    29    # sdc-key-id: <secret>
    30    # url defaults to us-west-1 DC, override if required
    31    # sdc-url: https://us-west-1.api.joyentcloud.com
    32  
    33    # Manta config
    34    # Can be set via env variables, or specified here
    35    # manta-user: <secret>
    36    # Can be set via env variables, or specified here
    37    # manta-key-id: <secret>
    38    # url defaults to us-east DC, override if required
    39    # manta-url: https://us-east.manta.joyent.com
    40  
    41    # Auth config
    42    # private-key-path is the private key used to sign Joyent requests.
    43    # Alternatively, you can supply "private-key" with the content of the private
    44    # key instead supplying the path to a file.
    45    # private-key-path: ~/.ssh/foo_id
    46    # algorithm defaults to rsa-sha256, override if required
    47    # algorithm: rsa-sha256
    48  
    49    # Whether or not to refresh the list of available updates for an
    50    # OS. The default option of true is recommended for use in
    51    # production systems, but disabling this can speed up local
    52    # deployments for development or testing.
    53    #
    54    # enable-os-refresh-update: true
    55  
    56    # Whether or not to perform OS upgrades when machines are
    57    # provisioned. The default option of true is recommended for use
    58    # in production systems, but disabling this can speed up local
    59    # deployments for development or testing.
    60    #
    61    # enable-os-upgrade: true
    62  
    63  `
    64  
    65  const (
    66  	SdcAccount          = "SDC_ACCOUNT"
    67  	SdcKeyId            = "SDC_KEY_ID"
    68  	SdcUrl              = "SDC_URL"
    69  	MantaUser           = "MANTA_USER"
    70  	MantaKeyId          = "MANTA_KEY_ID"
    71  	MantaUrl            = "MANTA_URL"
    72  	MantaPrivateKeyFile = "MANTA_PRIVATE_KEY_FILE"
    73  
    74  	sdcUser        = "sdc-user"
    75  	sdcKeyId       = "sdc-key-id"
    76  	sdcUrl         = "sdc-url"
    77  	mantaUser      = "manta-user"
    78  	mantaKeyId     = "manta-key-id"
    79  	mantaUrl       = "manta-url"
    80  	privateKeyPath = "private-key-path"
    81  	algorithm      = "algorithm"
    82  	controlDir     = "control-dir"
    83  	privateKey     = "private-key"
    84  )
    85  
    86  var environmentVariables = map[string]string{
    87  	sdcUser:        SdcAccount,
    88  	sdcKeyId:       SdcKeyId,
    89  	sdcUrl:         SdcUrl,
    90  	mantaUser:      MantaUser,
    91  	mantaKeyId:     MantaKeyId,
    92  	mantaUrl:       MantaUrl,
    93  	privateKeyPath: MantaPrivateKeyFile,
    94  }
    95  
    96  var configFields = schema.Fields{
    97  	sdcUser:        schema.String(),
    98  	sdcKeyId:       schema.String(),
    99  	sdcUrl:         schema.String(),
   100  	mantaUser:      schema.String(),
   101  	mantaKeyId:     schema.String(),
   102  	mantaUrl:       schema.String(),
   103  	privateKeyPath: schema.String(),
   104  	algorithm:      schema.String(),
   105  	controlDir:     schema.String(),
   106  	privateKey:     schema.String(),
   107  }
   108  
   109  var configDefaults = schema.Defaults{
   110  	sdcUrl:         "https://us-west-1.api.joyentcloud.com",
   111  	mantaUrl:       "https://us-east.manta.joyent.com",
   112  	algorithm:      "rsa-sha256",
   113  	privateKeyPath: schema.Omit,
   114  	sdcUser:        schema.Omit,
   115  	sdcKeyId:       schema.Omit,
   116  	mantaUser:      schema.Omit,
   117  	mantaKeyId:     schema.Omit,
   118  	privateKey:     schema.Omit,
   119  }
   120  
   121  var requiredFields = []string{
   122  	sdcUrl,
   123  	mantaUrl,
   124  	algorithm,
   125  	sdcUser,
   126  	sdcKeyId,
   127  	mantaUser,
   128  	mantaKeyId,
   129  	// privatekey and privatekeypath are handled separately
   130  }
   131  
   132  var configSecretFields = []string{
   133  	sdcUser,
   134  	sdcKeyId,
   135  	mantaUser,
   136  	mantaKeyId,
   137  	privateKey,
   138  }
   139  
   140  var configImmutableFields = []string{
   141  	sdcUrl,
   142  	mantaUrl,
   143  	privateKeyPath,
   144  	privateKey,
   145  	algorithm,
   146  }
   147  
   148  func validateConfig(cfg, old *config.Config) (*environConfig, error) {
   149  	// Check for valid changes for the base config values.
   150  	if err := config.Validate(cfg, old); err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	newAttrs, err := cfg.ValidateUnknownAttrs(configFields, configDefaults)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	envConfig := &environConfig{cfg, newAttrs}
   159  	// If an old config was supplied, check any immutable fields have not changed.
   160  	if old != nil {
   161  		oldEnvConfig, err := validateConfig(old, nil)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		for _, field := range configImmutableFields {
   166  			if oldEnvConfig.attrs[field] != envConfig.attrs[field] {
   167  				return nil, fmt.Errorf(
   168  					"%s: cannot change from %v to %v",
   169  					field, oldEnvConfig.attrs[field], envConfig.attrs[field],
   170  				)
   171  			}
   172  		}
   173  	}
   174  
   175  	// Read env variables to fill in any missing fields.
   176  	for field, envVar := range environmentVariables {
   177  		// If field is not set, get it from env variables
   178  		if nilOrEmptyString(envConfig.attrs[field]) {
   179  			localEnvVariable := os.Getenv(envVar)
   180  			if localEnvVariable != "" {
   181  				envConfig.attrs[field] = localEnvVariable
   182  			} else {
   183  				if field != privateKeyPath {
   184  					return nil, fmt.Errorf("cannot get %s value from environment variable %s", field, envVar)
   185  				}
   186  			}
   187  		}
   188  	}
   189  
   190  	if err := ensurePrivateKeyOrPath(envConfig); err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	// Now that we've ensured private-key-path is properly set, we go back and set
   195  	// up the private key - this is used to sign requests.
   196  	if nilOrEmptyString(envConfig.attrs[privateKey]) {
   197  		keyFile, err := utils.NormalizePath(envConfig.attrs[privateKeyPath].(string))
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		priv, err := ioutil.ReadFile(keyFile)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		envConfig.attrs[privateKey] = string(priv)
   206  	}
   207  
   208  	// Check for missing fields.
   209  	for _, field := range requiredFields {
   210  		if nilOrEmptyString(envConfig.attrs[field]) {
   211  			return nil, fmt.Errorf("%s: must not be empty", field)
   212  		}
   213  	}
   214  	return envConfig, nil
   215  }
   216  
   217  // Ensure private-key-path is set.
   218  func ensurePrivateKeyOrPath(envConfig *environConfig) error {
   219  	if !nilOrEmptyString(envConfig.attrs[privateKeyPath]) {
   220  		return nil
   221  	}
   222  	if path := os.Getenv(environmentVariables[privateKeyPath]); path != "" {
   223  		envConfig.attrs[privateKeyPath] = path
   224  		return nil
   225  	}
   226  	if !nilOrEmptyString(envConfig.attrs[privateKey]) {
   227  		return nil
   228  	}
   229  
   230  	return errors.New("no ssh private key specified in joyent configuration")
   231  }
   232  
   233  type environConfig struct {
   234  	*config.Config
   235  	attrs map[string]interface{}
   236  }
   237  
   238  func (ecfg *environConfig) GetAttrs() map[string]interface{} {
   239  	return ecfg.attrs
   240  }
   241  
   242  func (ecfg *environConfig) sdcUrl() string {
   243  	return ecfg.attrs[sdcUrl].(string)
   244  }
   245  
   246  func (ecfg *environConfig) sdcUser() string {
   247  	return ecfg.attrs[sdcUser].(string)
   248  }
   249  
   250  func (ecfg *environConfig) sdcKeyId() string {
   251  	return ecfg.attrs[sdcKeyId].(string)
   252  }
   253  
   254  func (ecfg *environConfig) mantaUrl() string {
   255  	return ecfg.attrs[mantaUrl].(string)
   256  }
   257  
   258  func (ecfg *environConfig) mantaUser() string {
   259  	return ecfg.attrs[mantaUser].(string)
   260  }
   261  
   262  func (ecfg *environConfig) mantaKeyId() string {
   263  	return ecfg.attrs[mantaKeyId].(string)
   264  }
   265  
   266  func (ecfg *environConfig) privateKey() string {
   267  	if v, ok := ecfg.attrs[privateKey]; ok {
   268  		return v.(string)
   269  	}
   270  	return ""
   271  }
   272  
   273  func (ecfg *environConfig) algorithm() string {
   274  	return ecfg.attrs[algorithm].(string)
   275  }
   276  
   277  func (c *environConfig) controlDir() string {
   278  	return c.attrs[controlDir].(string)
   279  }
   280  
   281  func (c *environConfig) ControlDir() string {
   282  	return c.controlDir()
   283  }
   284  
   285  func (ecfg *environConfig) SdcUrl() string {
   286  	return ecfg.sdcUrl()
   287  }
   288  
   289  func (ecfg *environConfig) Region() string {
   290  	sdcUrl := ecfg.sdcUrl()
   291  	// Check if running against local services
   292  	if isLocalhost(sdcUrl) {
   293  		return "some-region"
   294  	}
   295  	return sdcUrl[strings.LastIndex(sdcUrl, "/")+1 : strings.Index(sdcUrl, ".")]
   296  }
   297  
   298  func isLocalhost(u string) bool {
   299  	parsedUrl, err := url.Parse(u)
   300  	if err != nil {
   301  		return false
   302  	}
   303  	if strings.HasPrefix(parsedUrl.Host, "localhost") || strings.HasPrefix(parsedUrl.Host, "127.0.0.") {
   304  		return true
   305  	}
   306  
   307  	return false
   308  }
   309  
   310  func nilOrEmptyString(i interface{}) bool {
   311  	return i == nil || i == ""
   312  }