github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"log"
     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/cloud"
    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  // TODO(ericsnow) gologWriter can go away once loggo.Logger has a GoLogger() method.
    25  
    26  type gologWriter struct {
    27  	loggo.Logger
    28  	level loggo.Level
    29  }
    30  
    31  func newGoLogger() *log.Logger {
    32  	return log.New(&gologWriter{logger, loggo.TRACE}, "", 0)
    33  }
    34  
    35  func (w *gologWriter) Write(p []byte) (n int, err error) {
    36  	w.Logf(w.level, string(p))
    37  	return len(p), nil
    38  }
    39  
    40  type joyentProvider struct {
    41  	environProviderCredentials
    42  }
    43  
    44  var providerInstance = joyentProvider{}
    45  var _ environs.EnvironProvider = providerInstance
    46  
    47  var _ simplestreams.HasRegion = (*joyentEnviron)(nil)
    48  
    49  // PrepareConfig is part of the EnvironProvider interface.
    50  func (p joyentProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
    51  	if err := validateCloudSpec(args.Cloud); err != nil {
    52  		return nil, errors.Annotate(err, "validating cloud spec")
    53  	}
    54  	return args.Config, nil
    55  }
    56  
    57  const unauthorisedMessage = `
    58  Please ensure the SSH access key you have specified is correct.
    59  You can create or import an SSH key via the "Account Summary"
    60  page in the Joyent console.`
    61  
    62  // verifyCredentials issues a cheap, non-modifying request to Joyent to
    63  // verify the configured credentials. If verification fails, a user-friendly
    64  // error will be returned, and the original error will be logged at debug
    65  // level.
    66  var verifyCredentials = func(e *joyentEnviron) error {
    67  	creds, err := credentials(e.cloud)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	httpClient := client.NewClient(e.cloud.Endpoint, cloudapi.DefaultAPIVersion, creds, nil)
    72  	apiClient := cloudapi.New(httpClient)
    73  	_, err = apiClient.CountMachines()
    74  	if err != nil {
    75  		logger.Debugf("joyent request failed: %v", err)
    76  		if joyenterrors.IsInvalidCredentials(err) || joyenterrors.IsNotAuthorized(err) {
    77  			return errors.New("authentication failed.\n" + unauthorisedMessage)
    78  		}
    79  		return err
    80  	}
    81  	return nil
    82  }
    83  
    84  func credentials(cloud environs.CloudSpec) (*auth.Credentials, error) {
    85  	credAttrs := cloud.Credential.Attributes()
    86  	sdcUser := credAttrs[credAttrSDCUser]
    87  	sdcKeyID := credAttrs[credAttrSDCKeyID]
    88  	privateKey := credAttrs[credAttrPrivateKey]
    89  	algorithm := credAttrs[credAttrAlgorithm]
    90  	if algorithm == "" {
    91  		algorithm = algorithmDefault
    92  	}
    93  
    94  	authentication, err := auth.NewAuth(sdcUser, privateKey, algorithm)
    95  	if err != nil {
    96  		return nil, errors.Errorf("cannot create credentials: %v", err)
    97  	}
    98  	return &auth.Credentials{
    99  		UserAuthentication: authentication,
   100  		SdcKeyId:           sdcKeyID,
   101  		SdcEndpoint:        auth.Endpoint{URL: cloud.Endpoint},
   102  	}, nil
   103  }
   104  
   105  func (joyentProvider) Open(args environs.OpenParams) (environs.Environ, error) {
   106  	if err := validateCloudSpec(args.Cloud); err != nil {
   107  		return nil, errors.Annotate(err, "validating cloud spec")
   108  	}
   109  	env, err := newEnviron(args.Cloud, args.Config)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return env, nil
   114  }
   115  
   116  func (joyentProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   117  	newEcfg, err := validateConfig(cfg, old)
   118  	if err != nil {
   119  		return nil, errors.Errorf("invalid Joyent provider config: %v", err)
   120  	}
   121  	return cfg.Apply(newEcfg.attrs)
   122  }
   123  
   124  func GetProviderInstance() environs.EnvironProvider {
   125  	return providerInstance
   126  }
   127  
   128  // MetadataLookupParams returns parameters which are used to query image metadata to
   129  // find matching image information.
   130  func (p joyentProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   131  	if region == "" {
   132  		return nil, errors.Errorf("region must be specified")
   133  	}
   134  	return &simplestreams.MetadataLookupParams{
   135  		Region: region,
   136  	}, nil
   137  }
   138  
   139  func (p joyentProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   140  	valid, err := p.Validate(cfg, nil)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   145  }
   146  
   147  func validateCloudSpec(spec environs.CloudSpec) error {
   148  	if err := spec.Validate(); err != nil {
   149  		return errors.Trace(err)
   150  	}
   151  	if spec.Credential == nil {
   152  		return errors.NotValidf("missing credential")
   153  	}
   154  	if authType := spec.Credential.AuthType(); authType != cloud.UserPassAuthType {
   155  		return errors.NotSupportedf("%q auth-type", authType)
   156  	}
   157  	return nil
   158  }