github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/joyent/provider.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  
     9  	"github.com/joyent/gocommon/client"
    10  	joyenterrors "github.com/joyent/gocommon/errors"
    11  	"github.com/joyent/gosdc/cloudapi"
    12  	"github.com/joyent/gosign/auth"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/utils"
    16  
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/simplestreams"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.provider.joyent")
    23  
    24  type joyentProvider struct{}
    25  
    26  var providerInstance = joyentProvider{}
    27  var _ environs.EnvironProvider = providerInstance
    28  
    29  var _ simplestreams.HasRegion = (*joyentEnviron)(nil)
    30  
    31  var errNotImplemented = errors.New("not implemented in Joyent provider")
    32  
    33  // RestrictedConfigAttributes is specified in the EnvironProvider interface.
    34  func (joyentProvider) RestrictedConfigAttributes() []string {
    35  	return nil
    36  }
    37  
    38  // PrepareForCreateEnvironment is specified in the EnvironProvider interface.
    39  func (joyentProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
    40  	// Turn an incomplete config into a valid one, if possible.
    41  	attrs := cfg.UnknownAttrs()
    42  
    43  	if _, ok := attrs["control-dir"]; !ok {
    44  		uuid, err := utils.NewUUID()
    45  		if err != nil {
    46  			return nil, errors.Trace(err)
    47  		}
    48  		attrs["control-dir"] = fmt.Sprintf("%x", uuid.Raw())
    49  	}
    50  	return cfg.Apply(attrs)
    51  }
    52  
    53  func (p joyentProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
    54  	cfg, err := p.PrepareForCreateEnvironment(cfg)
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  	e, err := p.Open(cfg)
    59  	if err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  	if ctx.ShouldVerifyCredentials() {
    63  		if err := verifyCredentials(e.(*joyentEnviron)); err != nil {
    64  			return nil, errors.Trace(err)
    65  		}
    66  	}
    67  	return e, nil
    68  }
    69  
    70  const unauthorisedMessage = `
    71  Please ensure the Manta username and SSH access key you have
    72  specified are correct. You can create or import an SSH key via
    73  the "Account Summary" page in the Joyent console.`
    74  
    75  // verifyCredentials issues a cheap, non-modifying request to Joyent to
    76  // verify the configured credentials. If verification fails, a user-friendly
    77  // error will be returned, and the original error will be logged at debug
    78  // level.
    79  var verifyCredentials = func(e *joyentEnviron) error {
    80  	creds, err := credentials(e.Ecfg())
    81  	if err != nil {
    82  		return err
    83  	}
    84  	httpClient := client.NewClient(e.Ecfg().sdcUrl(), cloudapi.DefaultAPIVersion, creds, nil)
    85  	apiClient := cloudapi.New(httpClient)
    86  	_, err = apiClient.CountMachines()
    87  	if err != nil {
    88  		logger.Debugf("joyent request failed: %v", err)
    89  		if joyenterrors.IsInvalidCredentials(err) || joyenterrors.IsNotAuthorized(err) {
    90  			return errors.New("authentication failed.\n" + unauthorisedMessage)
    91  		}
    92  		return err
    93  	}
    94  	return nil
    95  }
    96  
    97  func credentials(cfg *environConfig) (*auth.Credentials, error) {
    98  	authentication, err := auth.NewAuth(cfg.mantaUser(), cfg.privateKey(), cfg.algorithm())
    99  	if err != nil {
   100  		return nil, errors.Errorf("cannot create credentials: %v", err)
   101  	}
   102  	return &auth.Credentials{
   103  		UserAuthentication: authentication,
   104  		MantaKeyId:         cfg.mantaKeyId(),
   105  		MantaEndpoint:      auth.Endpoint{URL: cfg.mantaUrl()},
   106  		SdcKeyId:           cfg.sdcKeyId(),
   107  		SdcEndpoint:        auth.Endpoint{URL: cfg.sdcUrl()},
   108  	}, nil
   109  }
   110  
   111  func (joyentProvider) Open(cfg *config.Config) (environs.Environ, error) {
   112  	env, err := newEnviron(cfg)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return env, nil
   117  }
   118  
   119  func (joyentProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   120  	newEcfg, err := validateConfig(cfg, old)
   121  	if err != nil {
   122  		return nil, errors.Errorf("invalid Joyent provider config: %v", err)
   123  	}
   124  	return cfg.Apply(newEcfg.attrs)
   125  }
   126  
   127  func (joyentProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   128  	// If you keep configSecretFields up to date, this method should Just Work.
   129  	ecfg, err := validateConfig(cfg, nil)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	secretAttrs := map[string]string{}
   134  	for _, field := range configSecretFields {
   135  		if value, ok := ecfg.attrs[field]; ok {
   136  			if stringValue, ok := value.(string); ok {
   137  				secretAttrs[field] = stringValue
   138  			} else {
   139  				// All your secret attributes must be strings at the moment. Sorry.
   140  				// It's an expedient and hopefully temporary measure that helps us
   141  				// plug a security hole in the API.
   142  				return nil, errors.Errorf(
   143  					"secret %q field must have a string value; got %v",
   144  					field, value,
   145  				)
   146  			}
   147  		}
   148  	}
   149  	return secretAttrs, nil
   150  }
   151  
   152  func (joyentProvider) BoilerplateConfig() string {
   153  	return boilerplateConfig
   154  
   155  }
   156  
   157  func GetProviderInstance() environs.EnvironProvider {
   158  	return providerInstance
   159  }
   160  
   161  // MetadataLookupParams returns parameters which are used to query image metadata to
   162  // find matching image information.
   163  func (p joyentProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   164  	if region == "" {
   165  		return nil, errors.Errorf("region must be specified")
   166  	}
   167  	return &simplestreams.MetadataLookupParams{
   168  		Region:        region,
   169  		Architectures: []string{"amd64", "armhf"},
   170  	}, nil
   171  }
   172  
   173  func (p joyentProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   174  	valid, err := p.Validate(cfg, nil)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   179  }