github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/gce/google/config.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package google
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/mail"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // The names of OS environment variables related to GCE.
    16  //
    17  // Note that these are not specified by Google. Instead they are
    18  // defined by juju for use with the GCE provider. If Google defines
    19  // equivalent environment variables they should be used instead.
    20  const (
    21  	OSEnvPrivateKey    = "GCE_PRIVATE_KEY"
    22  	OSEnvClientID      = "GCE_CLIENT_ID"
    23  	OSEnvClientEmail   = "GCE_CLIENT_EMAIL"
    24  	OSEnvRegion        = "GCE_REGION"
    25  	OSEnvProjectID     = "GCE_PROJECT_ID"
    26  	OSEnvImageEndpoint = "GCE_IMAGE_URL"
    27  )
    28  
    29  const (
    30  	jsonKeyTypeServiceAccount = "service_account"
    31  )
    32  
    33  // Credentials holds the OAuth2 credentials needed to authenticate on GCE.
    34  type Credentials struct {
    35  	// JSONKey is the content of the JSON key file for these credentials.
    36  	JSONKey []byte
    37  
    38  	// ClientID is the GCE account's OAuth ID. It is part of the OAuth
    39  	// config used in the OAuth-wrapping network transport.
    40  	ClientID string
    41  
    42  	// ClientEmail is the email address associatd with the GCE account.
    43  	// It is used to generate a new OAuth token to use in the
    44  	// OAuth-wrapping network transport.
    45  	ClientEmail string
    46  
    47  	// PrivateKey is the private key that matches the public key
    48  	// associatd with the GCE account. It is used to generate a new
    49  	// OAuth token to use in the OAuth-wrapping network transport.
    50  	PrivateKey []byte
    51  }
    52  
    53  // NewCredentials returns a new Credentials based on the provided
    54  // values. The keys must be recognized OS env var names for the
    55  // different credential fields.
    56  func NewCredentials(values map[string]string) (*Credentials, error) {
    57  	var creds Credentials
    58  	for k, v := range values {
    59  		switch k {
    60  		case OSEnvClientID:
    61  			creds.ClientID = v
    62  		case OSEnvClientEmail:
    63  			creds.ClientEmail = v
    64  		case OSEnvPrivateKey:
    65  			creds.PrivateKey = []byte(v)
    66  		default:
    67  			return nil, errors.NotSupportedf("key %q", k)
    68  		}
    69  	}
    70  
    71  	if err := creds.Validate(); err == nil {
    72  		jk, err := creds.buildJSONKey()
    73  		if err != nil {
    74  			return nil, errors.Trace(err)
    75  		}
    76  		creds.JSONKey = jk
    77  	}
    78  
    79  	return &creds, nil
    80  }
    81  
    82  // ParseJSONKey returns a new Credentials with values based on the
    83  // provided JSON key file contents.
    84  func ParseJSONKey(jsonKeyFile io.Reader) (*Credentials, error) {
    85  	jsonKey, err := ioutil.ReadAll(jsonKeyFile)
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	values, err := parseJSONKey(jsonKey)
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	delete(values, "type")
    94  	delete(values, "private_key_id")
    95  	creds, err := NewCredentials(values)
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	creds.JSONKey = jsonKey
   100  	return creds, nil
   101  }
   102  
   103  // parseJSONKey extracts the auth information from the JSON file
   104  // downloaded from the GCE console (under /apiui/credential).
   105  func parseJSONKey(jsonKey []byte) (map[string]string, error) {
   106  	data := make(map[string]string)
   107  	if err := json.Unmarshal(jsonKey, &data); err != nil {
   108  		return nil, errors.Trace(err)
   109  	}
   110  
   111  	keyType, ok := data["type"]
   112  	if !ok {
   113  		return nil, errors.New(`missing "type"`)
   114  	}
   115  	switch keyType {
   116  	case jsonKeyTypeServiceAccount:
   117  		for k, v := range data {
   118  			switch k {
   119  			case "private_key":
   120  				data[OSEnvPrivateKey] = v
   121  				delete(data, k)
   122  			case "client_email":
   123  				data[OSEnvClientEmail] = v
   124  				delete(data, k)
   125  			case "client_id":
   126  				data[OSEnvClientID] = v
   127  				delete(data, k)
   128  			}
   129  		}
   130  	default:
   131  		return nil, errors.NotSupportedf("JSON key type %q", data["type"])
   132  	}
   133  	return data, nil
   134  }
   135  
   136  // buildJSONKey returns the content of the JSON key file for the
   137  // credential values.
   138  func (gc Credentials) buildJSONKey() ([]byte, error) {
   139  	return json.Marshal(&map[string]string{
   140  		"type":         jsonKeyTypeServiceAccount,
   141  		"client_id":    gc.ClientID,
   142  		"client_email": gc.ClientEmail,
   143  		"private_key":  string(gc.PrivateKey),
   144  	})
   145  }
   146  
   147  // Values returns the credentials as a simple mapping with the
   148  // corresponding OS env variable names as the keys.
   149  func (gc Credentials) Values() map[string]string {
   150  	return map[string]string{
   151  		OSEnvClientID:    gc.ClientID,
   152  		OSEnvClientEmail: gc.ClientEmail,
   153  		OSEnvPrivateKey:  string(gc.PrivateKey),
   154  	}
   155  }
   156  
   157  // Validate checks the credentialss for invalid values. If the values
   158  // are not valid, it returns errors.NotValid with the message set to
   159  // the corresponding OS environment variable name.
   160  //
   161  // To be considered valid, each of the credentials must be set to some
   162  // non-empty value. Furthermore, ClientEmail must be a proper email
   163  // address.
   164  func (gc Credentials) Validate() error {
   165  	if gc.ClientID == "" {
   166  		return NewMissingConfigValue(OSEnvClientID, "ClientID")
   167  	}
   168  	if gc.ClientEmail == "" {
   169  		return NewMissingConfigValue(OSEnvClientEmail, "ClientEmail")
   170  	}
   171  	if _, err := mail.ParseAddress(gc.ClientEmail); err != nil {
   172  		return NewInvalidConfigValue(OSEnvClientEmail, gc.ClientEmail, err)
   173  	}
   174  	if len(gc.PrivateKey) == 0 {
   175  		return NewMissingConfigValue(OSEnvPrivateKey, "PrivateKey")
   176  	}
   177  	return nil
   178  }
   179  
   180  // ConnectionConfig contains the config values used for a connection
   181  // to the GCE API.
   182  type ConnectionConfig struct {
   183  	// Region is the GCE region in which to operate for the connection.
   184  	Region string
   185  
   186  	// ProjectID is the project ID to use in all GCE API requests for
   187  	// the connection.
   188  	ProjectID string
   189  }
   190  
   191  // Validate checks the connection's fields for invalid values.
   192  // If the values are not valid, it returns a config.InvalidConfigValue
   193  // error with the key set to the corresponding OS environment variable
   194  // name.
   195  //
   196  // To be considered valid, each of the connection's must be set to some
   197  // non-empty value.
   198  func (gc ConnectionConfig) Validate() error {
   199  	if gc.Region == "" {
   200  		return NewMissingConfigValue(OSEnvRegion, "Region")
   201  	}
   202  	if gc.ProjectID == "" {
   203  		return NewMissingConfigValue(OSEnvProjectID, "ProjectID")
   204  	}
   205  	return nil
   206  }