github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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  	"os"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/schema"
    11  
    12  	"github.com/juju/juju/environs/config"
    13  	"github.com/juju/juju/provider/gce/google"
    14  )
    15  
    16  // TODO(ericsnow) While not strictly config-related, we could use some
    17  // mechanism by which we can validate the values we've hard-coded in
    18  // this provider match up with the external authoritative sources. One
    19  // example of this is the data stored in instancetypes.go. Similarly
    20  // we should also ensure the cloud-images metadata is correct and
    21  // up-to-date, though that is more the responsibility of that team.
    22  // Regardless, it may be useful to include a tool somewhere in juju
    23  // that we can use to validate this provider's potentially out-of-date
    24  // data.
    25  
    26  // The GCE-specific config keys.
    27  const (
    28  	cfgAuthFile      = "auth-file"
    29  	cfgPrivateKey    = "private-key"
    30  	cfgClientID      = "client-id"
    31  	cfgClientEmail   = "client-email"
    32  	cfgRegion        = "region"
    33  	cfgProjectID     = "project-id"
    34  	cfgImageEndpoint = "image-endpoint"
    35  )
    36  
    37  // boilerplateConfig will be shown in help output, so please keep it up to
    38  // date when you change environment configuration below.
    39  var boilerplateConfig = `
    40  gce:
    41    type: gce
    42  
    43    # Google Auth Info
    44    # The GCE provider uses OAuth to authenticate. This requires that
    45    # you set it up and get the relevant credentials. For more information
    46    # see https://cloud.google.com/compute/docs/api/how-tos/authorization.
    47    # The key information can be downloaded as a JSON file, or copied, from:
    48    #   https://console.developers.google.com/project/<projet>/apiui/credential
    49    # Either set the path to the downloaded JSON file here:
    50    auth-file:
    51  
    52    # ...or set the individual fields for the credentials. Either way, all
    53    # three of these are required and have specific meaning to GCE.
    54    # private-key:
    55    # client-email:
    56    # client-id:
    57  
    58    # Google instance info
    59    # To provision instances and perform related operations, the provider
    60    # will need to know which GCE project to use and into which region to
    61    # provision. While the region has a default, the project ID is
    62    # required. For information on the project ID, see
    63    # https://cloud.google.com/compute/docs/projects and regarding regions
    64    # see https://cloud.google.com/compute/docs/zones.
    65    project-id:
    66    # region: us-central1
    67  
    68    # The GCE provider uses pre-built images when provisioning instances.
    69    # You can customize the location in which to find them with the
    70    # image-endpoint setting. The default value is the a location within
    71    # GCE, so it will give you the best speed when bootstrapping or adding
    72    # machines. For more information on the image cache see
    73    # https://cloud-images.ubuntu.com/.
    74    # image-endpoint: https://www.googleapis.com
    75  `[1:]
    76  
    77  // configFields is the spec for each GCE config value's type.
    78  var configFields = schema.Fields{
    79  	cfgAuthFile:      schema.String(),
    80  	cfgPrivateKey:    schema.String(),
    81  	cfgClientID:      schema.String(),
    82  	cfgClientEmail:   schema.String(),
    83  	cfgRegion:        schema.String(),
    84  	cfgProjectID:     schema.String(),
    85  	cfgImageEndpoint: schema.String(),
    86  }
    87  
    88  // TODO(ericsnow) Do we need custom defaults for "image-metadata-url" or
    89  // "agent-metadata-url"? The defaults are the official ones (e.g.
    90  // cloud-images).
    91  
    92  var configDefaults = schema.Defaults{
    93  	cfgAuthFile: "",
    94  	// See http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:gce.json
    95  	cfgImageEndpoint: "https://www.googleapis.com",
    96  	cfgRegion:        "us-central1",
    97  }
    98  
    99  var configSecretFields = []string{
   100  	cfgPrivateKey,
   101  }
   102  
   103  var configImmutableFields = []string{
   104  	cfgAuthFile,
   105  	cfgPrivateKey,
   106  	cfgClientID,
   107  	cfgClientEmail,
   108  	cfgRegion,
   109  	cfgProjectID,
   110  	cfgImageEndpoint,
   111  }
   112  
   113  var configAuthFields = []string{
   114  	cfgPrivateKey,
   115  	cfgClientID,
   116  	cfgClientEmail,
   117  }
   118  
   119  // osEnvFields is the mapping from GCE env vars to config keys.
   120  var osEnvFields = map[string]string{
   121  	google.OSEnvPrivateKey:    cfgPrivateKey,
   122  	google.OSEnvClientID:      cfgClientID,
   123  	google.OSEnvClientEmail:   cfgClientEmail,
   124  	google.OSEnvRegion:        cfgRegion,
   125  	google.OSEnvProjectID:     cfgProjectID,
   126  	google.OSEnvImageEndpoint: cfgImageEndpoint,
   127  }
   128  
   129  func parseOSEnv() (map[string]interface{}, error) {
   130  	// TODO(ericsnow) Support pulling ID/PK from shell environment variables.
   131  	return nil, nil
   132  }
   133  
   134  // handleInvalidField converts a google.InvalidConfigValue into a new
   135  // error, translating a {provider/gce/google}.OSEnvVar* value into a
   136  // GCE config key in the new error.
   137  func handleInvalidField(err error) error {
   138  	vErr := err.(*google.InvalidConfigValue)
   139  	if strValue, ok := vErr.Value.(string); ok && strValue == "" {
   140  		key := osEnvFields[vErr.Key]
   141  		return errors.Errorf("%s: must not be empty", key)
   142  	}
   143  	return err
   144  }
   145  
   146  type environConfig struct {
   147  	*config.Config
   148  	attrs       map[string]interface{}
   149  	credentials *google.Credentials
   150  }
   151  
   152  // newConfig builds a new environConfig from the provided Config and
   153  // returns it.
   154  func newConfig(cfg *config.Config) *environConfig {
   155  	return &environConfig{
   156  		Config: cfg,
   157  		attrs:  cfg.UnknownAttrs(),
   158  	}
   159  }
   160  
   161  // newValidConfig builds a new environConfig from the provided Config
   162  // and returns it. This includes applying the provided defaults
   163  // values, if any. The resulting config values are validated.
   164  func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) {
   165  	credentials, err := parseCredentials(cfg)
   166  	if err != nil {
   167  		return nil, errors.Trace(err)
   168  	}
   169  	handled, err := applyCredentials(cfg, credentials)
   170  	if err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	cfg = handled
   174  
   175  	// Ensure that the provided config is valid.
   176  	if err := config.Validate(cfg, nil); err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  
   180  	// Apply the defaults and coerce/validate the custom config attrs.
   181  	validated, err := cfg.ValidateUnknownAttrs(configFields, defaults)
   182  	if err != nil {
   183  		return nil, errors.Trace(err)
   184  	}
   185  	validCfg, err := cfg.Apply(validated)
   186  	if err != nil {
   187  		return nil, errors.Trace(err)
   188  	}
   189  
   190  	// Build the config.
   191  	ecfg := newConfig(validCfg)
   192  	ecfg.credentials = credentials
   193  
   194  	// Do final validation.
   195  	if err := ecfg.validate(); err != nil {
   196  		return nil, errors.Trace(err)
   197  	}
   198  
   199  	return ecfg, nil
   200  }
   201  
   202  func (c *environConfig) authFile() string {
   203  	if c.attrs[cfgAuthFile] == nil {
   204  		return ""
   205  	}
   206  	return c.attrs[cfgAuthFile].(string)
   207  }
   208  
   209  func (c *environConfig) privateKey() string {
   210  	return c.attrs[cfgPrivateKey].(string)
   211  }
   212  
   213  func (c *environConfig) clientID() string {
   214  	return c.attrs[cfgClientID].(string)
   215  }
   216  
   217  func (c *environConfig) clientEmail() string {
   218  	return c.attrs[cfgClientEmail].(string)
   219  }
   220  
   221  func (c *environConfig) region() string {
   222  	return c.attrs[cfgRegion].(string)
   223  }
   224  
   225  func (c *environConfig) projectID() string {
   226  	return c.attrs[cfgProjectID].(string)
   227  }
   228  
   229  // imageEndpoint identifies where the provider should look for
   230  // cloud images (i.e. for simplestreams).
   231  func (c *environConfig) imageEndpoint() string {
   232  	return c.attrs[cfgImageEndpoint].(string)
   233  }
   234  
   235  // auth build a new Credentials based on the config and returns it.
   236  func (c *environConfig) auth() *google.Credentials {
   237  	if c.credentials == nil {
   238  		c.credentials = &google.Credentials{
   239  			ClientID:    c.clientID(),
   240  			ClientEmail: c.clientEmail(),
   241  			PrivateKey:  []byte(c.privateKey()),
   242  		}
   243  	}
   244  	return c.credentials
   245  }
   246  
   247  // newConnection build a ConnectionConfig based on the config and returns it.
   248  func (c *environConfig) newConnection() google.ConnectionConfig {
   249  	return google.ConnectionConfig{
   250  		Region:    c.region(),
   251  		ProjectID: c.projectID(),
   252  	}
   253  }
   254  
   255  // secret gathers the "secret" config values and returns them.
   256  func (c *environConfig) secret() map[string]string {
   257  	secretAttrs := make(map[string]string, len(configSecretFields))
   258  	for _, key := range configSecretFields {
   259  		secretAttrs[key] = c.attrs[key].(string)
   260  	}
   261  	return secretAttrs
   262  }
   263  
   264  // validate checks GCE-specific config values.
   265  func (c environConfig) validate() error {
   266  	// All fields must be populated, even with just the default.
   267  	for field := range configFields {
   268  		if dflt, ok := configDefaults[field]; ok && dflt == "" {
   269  			continue
   270  		}
   271  		if c.attrs[field].(string) == "" {
   272  			return errors.Errorf("%s: must not be empty", field)
   273  		}
   274  	}
   275  
   276  	// Check sanity of GCE fields.
   277  	if err := c.auth().Validate(); err != nil {
   278  		return errors.Trace(handleInvalidField(err))
   279  	}
   280  	if err := c.newConnection().Validate(); err != nil {
   281  		return errors.Trace(handleInvalidField(err))
   282  	}
   283  
   284  	return nil
   285  }
   286  
   287  // update applies changes from the provided config to the env config.
   288  // Changes to any immutable attributes result in an error.
   289  func (c *environConfig) update(cfg *config.Config) error {
   290  	// Validate the updates. newValidConfig does not modify the "known"
   291  	// config attributes so it is safe to call Validate here first.
   292  	if err := config.Validate(cfg, c.Config); err != nil {
   293  		return errors.Trace(err)
   294  	}
   295  
   296  	updates, err := newValidConfig(cfg, configDefaults)
   297  	if err != nil {
   298  		return errors.Trace(err)
   299  	}
   300  
   301  	// Check that no immutable fields have changed.
   302  	attrs := updates.UnknownAttrs()
   303  	for _, field := range configImmutableFields {
   304  		if attrs[field] != c.attrs[field] {
   305  			return errors.Errorf("%s: cannot change from %v to %v", field, c.attrs[field], attrs[field])
   306  		}
   307  	}
   308  
   309  	// Apply the updates.
   310  	c.Config = cfg
   311  	c.attrs = cfg.UnknownAttrs()
   312  	return nil
   313  }
   314  
   315  // parseCredentials extracts the OAuth2 info from the config from the
   316  // individual fields (falling back on the JSON file).
   317  func parseCredentials(cfg *config.Config) (*google.Credentials, error) {
   318  	attrs := cfg.UnknownAttrs()
   319  
   320  	// Try the auth fields first.
   321  	values := make(map[string]string)
   322  	for _, field := range configAuthFields {
   323  		if existing, ok := attrs[field].(string); ok && existing != "" {
   324  			for key, candidate := range osEnvFields {
   325  				if field == candidate {
   326  					values[key] = existing
   327  					break
   328  				}
   329  			}
   330  		}
   331  	}
   332  	if len(values) > 0 {
   333  		creds, err := google.NewCredentials(values)
   334  		if err != nil {
   335  			return nil, errors.Trace(err)
   336  		}
   337  		return creds, nil
   338  	}
   339  
   340  	// Fall back to the auth file.
   341  	filename, ok := attrs[cfgAuthFile].(string)
   342  	if !ok || filename == "" {
   343  		// The missing credentials will be caught later.
   344  		return nil, nil
   345  	}
   346  	authFile, err := os.Open(filename)
   347  	if err != nil {
   348  		return nil, errors.Trace(err)
   349  	}
   350  	defer authFile.Close()
   351  	creds, err := google.ParseJSONKey(authFile)
   352  	if err != nil {
   353  		return nil, errors.Trace(err)
   354  	}
   355  	return creds, nil
   356  }
   357  
   358  func applyCredentials(cfg *config.Config, creds *google.Credentials) (*config.Config, error) {
   359  	updates := make(map[string]interface{})
   360  	for k, v := range creds.Values() {
   361  		if v == "" {
   362  			continue
   363  		}
   364  		if field, ok := osEnvFields[k]; ok {
   365  			for _, authField := range configAuthFields {
   366  				if field == authField {
   367  					updates[field] = v
   368  					break
   369  				}
   370  			}
   371  		}
   372  	}
   373  	updated, err := cfg.Apply(updates)
   374  	if err != nil {
   375  		return nil, errors.Trace(err)
   376  	}
   377  	return updated, nil
   378  }