github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/gce/config.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/schema"
     9  
    10  	"github.com/juju/juju/environs/config"
    11  	"github.com/juju/juju/provider/gce/google"
    12  )
    13  
    14  // TODO(ericsnow) While not strictly config-related, we could use some
    15  // mechanism by which we can validate the values we've hard-coded in
    16  // this provider match up with the external authoritative sources. One
    17  // example of this is the data stored in instancetypes.go. Similarly
    18  // we should also ensure the cloud-images metadata is correct and
    19  // up-to-date, though that is more the responsibility of that team.
    20  // Regardless, it may be useful to include a tool somewhere in juju
    21  // that we can use to validate this provider's potentially out-of-date
    22  // data.
    23  
    24  // The GCE-specific config keys.
    25  const (
    26  	cfgPrivateKey    = "private-key"
    27  	cfgClientID      = "client-id"
    28  	cfgClientEmail   = "client-email"
    29  	cfgRegion        = "region"
    30  	cfgProjectID     = "project-id"
    31  	cfgImageEndpoint = "image-endpoint"
    32  )
    33  
    34  // configFields is the spec for each GCE config value's type.
    35  var configFields = schema.Fields{
    36  	cfgPrivateKey:    schema.String(),
    37  	cfgClientID:      schema.String(),
    38  	cfgClientEmail:   schema.String(),
    39  	cfgRegion:        schema.String(),
    40  	cfgProjectID:     schema.String(),
    41  	cfgImageEndpoint: schema.String(),
    42  }
    43  
    44  // TODO(ericsnow) Do we need custom defaults for "image-metadata-url" or
    45  // "agent-metadata-url"? The defaults are the official ones (e.g.
    46  // cloud-images).
    47  
    48  var configDefaults = schema.Defaults{
    49  	// See http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:gce.json
    50  	cfgImageEndpoint: "https://www.googleapis.com",
    51  	cfgRegion:        "us-central1",
    52  }
    53  
    54  var configSecretFields = []string{
    55  	cfgPrivateKey,
    56  }
    57  
    58  var configImmutableFields = []string{
    59  	cfgPrivateKey,
    60  	cfgClientID,
    61  	cfgClientEmail,
    62  	cfgRegion,
    63  	cfgProjectID,
    64  	cfgImageEndpoint,
    65  }
    66  
    67  var configAuthFields = []string{
    68  	cfgPrivateKey,
    69  	cfgProjectID,
    70  	cfgClientID,
    71  	cfgClientEmail,
    72  }
    73  
    74  // osEnvFields is the mapping from GCE env vars to config keys.
    75  var osEnvFields = map[string]string{
    76  	google.OSEnvPrivateKey:    cfgPrivateKey,
    77  	google.OSEnvClientID:      cfgClientID,
    78  	google.OSEnvClientEmail:   cfgClientEmail,
    79  	google.OSEnvRegion:        cfgRegion,
    80  	google.OSEnvProjectID:     cfgProjectID,
    81  	google.OSEnvImageEndpoint: cfgImageEndpoint,
    82  }
    83  
    84  // handleInvalidField converts a google.InvalidConfigValue into a new
    85  // error, translating a {provider/gce/google}.OSEnvVar* value into a
    86  // GCE config key in the new error.
    87  func handleInvalidField(err error) error {
    88  	vErr := err.(*google.InvalidConfigValue)
    89  	if strValue, ok := vErr.Value.(string); ok && strValue == "" {
    90  		key := osEnvFields[vErr.Key]
    91  		return errors.Errorf("%s: must not be empty", key)
    92  	}
    93  	return err
    94  }
    95  
    96  type environConfig struct {
    97  	*config.Config
    98  	attrs       map[string]interface{}
    99  	credentials *google.Credentials
   100  }
   101  
   102  // newConfig builds a new environConfig from the provided Config and
   103  // returns it.
   104  func newConfig(cfg *config.Config) *environConfig {
   105  	return &environConfig{
   106  		Config: cfg,
   107  		attrs:  cfg.UnknownAttrs(),
   108  	}
   109  }
   110  
   111  // newValidConfig builds a new environConfig from the provided Config
   112  // and returns it. This includes applying the provided defaults
   113  // values, if any. The resulting config values are validated.
   114  func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) {
   115  	credentials, err := parseCredentials(cfg)
   116  	if err != nil {
   117  		return nil, errors.Trace(err)
   118  	}
   119  	handled, err := applyCredentials(cfg, credentials)
   120  	if err != nil {
   121  		return nil, errors.Trace(err)
   122  	}
   123  	cfg = handled
   124  
   125  	// Ensure that the provided config is valid.
   126  	if err := config.Validate(cfg, nil); err != nil {
   127  		return nil, errors.Trace(err)
   128  	}
   129  
   130  	// Apply the defaults and coerce/validate the custom config attrs.
   131  	validated, err := cfg.ValidateUnknownAttrs(configFields, defaults)
   132  	if err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  	validCfg, err := cfg.Apply(validated)
   136  	if err != nil {
   137  		return nil, errors.Trace(err)
   138  	}
   139  
   140  	// Build the config.
   141  	ecfg := newConfig(validCfg)
   142  	ecfg.credentials = credentials
   143  
   144  	// Do final validation.
   145  	if err := ecfg.validate(); err != nil {
   146  		return nil, errors.Trace(err)
   147  	}
   148  
   149  	return ecfg, nil
   150  }
   151  
   152  func (c *environConfig) privateKey() string {
   153  	return c.attrs[cfgPrivateKey].(string)
   154  }
   155  
   156  func (c *environConfig) clientID() string {
   157  	return c.attrs[cfgClientID].(string)
   158  }
   159  
   160  func (c *environConfig) clientEmail() string {
   161  	return c.attrs[cfgClientEmail].(string)
   162  }
   163  
   164  func (c *environConfig) region() string {
   165  	return c.attrs[cfgRegion].(string)
   166  }
   167  
   168  func (c *environConfig) projectID() string {
   169  	return c.attrs[cfgProjectID].(string)
   170  }
   171  
   172  // imageEndpoint identifies where the provider should look for
   173  // cloud images (i.e. for simplestreams).
   174  func (c *environConfig) imageEndpoint() string {
   175  	return c.attrs[cfgImageEndpoint].(string)
   176  }
   177  
   178  // auth build a new Credentials based on the config and returns it.
   179  func (c *environConfig) auth() *google.Credentials {
   180  	if c.credentials == nil {
   181  		c.credentials = &google.Credentials{
   182  			ClientID:    c.clientID(),
   183  			ClientEmail: c.clientEmail(),
   184  			PrivateKey:  []byte(c.privateKey()),
   185  		}
   186  	}
   187  	return c.credentials
   188  }
   189  
   190  // newConnection build a ConnectionConfig based on the config and returns it.
   191  func (c *environConfig) newConnection() google.ConnectionConfig {
   192  	return google.ConnectionConfig{
   193  		Region:    c.region(),
   194  		ProjectID: c.projectID(),
   195  	}
   196  }
   197  
   198  // secret gathers the "secret" config values and returns them.
   199  func (c *environConfig) secret() map[string]string {
   200  	secretAttrs := make(map[string]string, len(configSecretFields))
   201  	for _, key := range configSecretFields {
   202  		secretAttrs[key] = c.attrs[key].(string)
   203  	}
   204  	return secretAttrs
   205  }
   206  
   207  // validate checks GCE-specific config values.
   208  func (c environConfig) validate() error {
   209  	// All fields must be populated, even with just the default.
   210  	for field := range configFields {
   211  		if dflt, ok := configDefaults[field]; ok && dflt == "" {
   212  			continue
   213  		}
   214  		if c.attrs[field].(string) == "" {
   215  			return errors.Errorf("%s: must not be empty", field)
   216  		}
   217  	}
   218  
   219  	// Check sanity of GCE fields.
   220  	if err := c.auth().Validate(); err != nil {
   221  		return errors.Trace(handleInvalidField(err))
   222  	}
   223  	if err := c.newConnection().Validate(); err != nil {
   224  		return errors.Trace(handleInvalidField(err))
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  // update applies changes from the provided config to the env config.
   231  // Changes to any immutable attributes result in an error.
   232  func (c *environConfig) update(cfg *config.Config) error {
   233  	// Validate the updates. newValidConfig does not modify the "known"
   234  	// config attributes so it is safe to call Validate here first.
   235  	if err := config.Validate(cfg, c.Config); err != nil {
   236  		return errors.Trace(err)
   237  	}
   238  
   239  	updates, err := newValidConfig(cfg, configDefaults)
   240  	if err != nil {
   241  		return errors.Trace(err)
   242  	}
   243  
   244  	// Check that no immutable fields have changed.
   245  	attrs := updates.UnknownAttrs()
   246  	for _, field := range configImmutableFields {
   247  		if attrs[field] != c.attrs[field] {
   248  			return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field])
   249  		}
   250  	}
   251  
   252  	// Apply the updates.
   253  	c.Config = cfg
   254  	c.attrs = cfg.UnknownAttrs()
   255  	return nil
   256  }
   257  
   258  // parseCredentials extracts the OAuth2 info from the config from the
   259  // individual fields (falling back on the JSON file).
   260  func parseCredentials(cfg *config.Config) (*google.Credentials, error) {
   261  	attrs := cfg.UnknownAttrs()
   262  	values := make(map[string]string)
   263  	for _, field := range configAuthFields {
   264  		if existing, ok := attrs[field].(string); ok && existing != "" {
   265  			for key, candidate := range osEnvFields {
   266  				if field == candidate {
   267  					values[key] = existing
   268  					break
   269  				}
   270  			}
   271  		}
   272  	}
   273  	return google.NewCredentials(values)
   274  }
   275  
   276  func applyCredentials(cfg *config.Config, creds *google.Credentials) (*config.Config, error) {
   277  	updates := make(map[string]interface{})
   278  	for k, v := range creds.Values() {
   279  		if v == "" {
   280  			continue
   281  		}
   282  		if field, ok := osEnvFields[k]; ok {
   283  			for _, authField := range configAuthFields {
   284  				if field == authField {
   285  					updates[field] = v
   286  					break
   287  				}
   288  			}
   289  		}
   290  	}
   291  	updated, err := cfg.Apply(updates)
   292  	if err != nil {
   293  		return nil, errors.Trace(err)
   294  	}
   295  	return updated, nil
   296  }