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