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