github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/jsonschema"
    15  	"github.com/juju/loggo"
    16  
    17  	"github.com/juju/juju/cloud"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/environs/context"
    21  	"github.com/juju/juju/environs/simplestreams"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.provider.joyent")
    25  
    26  // TODO(ericsnow) gologWriter can go away once loggo.Logger has a GoLogger() method.
    27  
    28  type gologWriter struct {
    29  	loggo.Logger
    30  	level loggo.Level
    31  }
    32  
    33  func newGoLogger() *log.Logger {
    34  	return log.New(&gologWriter{logger, loggo.TRACE}, "", 0)
    35  }
    36  
    37  func (w *gologWriter) Write(p []byte) (n int, err error) {
    38  	w.Logf(w.level, string(p))
    39  	return len(p), nil
    40  }
    41  
    42  type joyentProvider struct {
    43  	environProviderCredentials
    44  }
    45  
    46  var providerInstance = joyentProvider{}
    47  var _ environs.EnvironProvider = providerInstance
    48  
    49  var _ simplestreams.HasRegion = (*joyentEnviron)(nil)
    50  
    51  // CloudSchema returns the schema used to validate input for add-cloud.  Since
    52  // this provider does not support custom clouds, this always returns nil.
    53  func (p joyentProvider) CloudSchema() *jsonschema.Schema {
    54  	return nil
    55  }
    56  
    57  // Ping tests the connection to the cloud, to verify the endpoint is valid.
    58  func (p joyentProvider) Ping(ctx context.ProviderCallContext, endpoint string) error {
    59  	return errors.NotImplementedf("Ping")
    60  }
    61  
    62  // PrepareConfig is part of the EnvironProvider interface.
    63  func (p joyentProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
    64  	if err := validateCloudSpec(args.Cloud); err != nil {
    65  		return nil, errors.Annotate(err, "validating cloud spec")
    66  	}
    67  	return args.Config, nil
    68  }
    69  
    70  const unauthorisedMessage = `
    71  Please ensure the SSH access key you have specified is correct.
    72  You can create or import an SSH key via the "Account Summary"
    73  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.cloud)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	httpClient := client.NewClient(e.cloud.Endpoint, 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(cloud environs.CloudSpec) (*auth.Credentials, error) {
    98  	credAttrs := cloud.Credential.Attributes()
    99  	sdcUser := credAttrs[credAttrSDCUser]
   100  	sdcKeyID := credAttrs[credAttrSDCKeyID]
   101  	privateKey := credAttrs[credAttrPrivateKey]
   102  	algorithm := credAttrs[credAttrAlgorithm]
   103  	if algorithm == "" {
   104  		algorithm = algorithmDefault
   105  	}
   106  
   107  	authentication, err := auth.NewAuth(sdcUser, privateKey, algorithm)
   108  	if err != nil {
   109  		return nil, errors.Errorf("cannot create credentials: %v", err)
   110  	}
   111  	return &auth.Credentials{
   112  		UserAuthentication: authentication,
   113  		SdcKeyId:           sdcKeyID,
   114  		SdcEndpoint:        auth.Endpoint{URL: cloud.Endpoint},
   115  	}, nil
   116  }
   117  
   118  // Version is part of the EnvironProvider interface.
   119  func (joyentProvider) Version() int {
   120  	return 0
   121  }
   122  
   123  func (joyentProvider) Open(args environs.OpenParams) (environs.Environ, error) {
   124  	if err := validateCloudSpec(args.Cloud); err != nil {
   125  		return nil, errors.Annotate(err, "validating cloud spec")
   126  	}
   127  	env, err := newEnviron(args.Cloud, args.Config)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	return env, nil
   132  }
   133  
   134  func (joyentProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   135  	newEcfg, err := validateConfig(cfg, old)
   136  	if err != nil {
   137  		return nil, errors.Errorf("invalid Joyent provider config: %v", err)
   138  	}
   139  	return cfg.Apply(newEcfg.attrs)
   140  }
   141  
   142  func GetProviderInstance() environs.EnvironProvider {
   143  	return providerInstance
   144  }
   145  
   146  // MetadataLookupParams returns parameters which are used to query image metadata to
   147  // find matching image information.
   148  func (p joyentProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   149  	if region == "" {
   150  		return nil, errors.Errorf("region must be specified")
   151  	}
   152  	return &simplestreams.MetadataLookupParams{
   153  		Region: region,
   154  	}, nil
   155  }
   156  
   157  func (p joyentProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   158  	valid, err := p.Validate(cfg, nil)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   163  }
   164  
   165  func validateCloudSpec(spec environs.CloudSpec) error {
   166  	if err := spec.Validate(); err != nil {
   167  		return errors.Trace(err)
   168  	}
   169  	if spec.Credential == nil {
   170  		return errors.NotValidf("missing credential")
   171  	}
   172  	if authType := spec.Credential.AuthType(); authType != cloud.UserPassAuthType {
   173  		return errors.NotSupportedf("%q auth-type", authType)
   174  	}
   175  	return nil
   176  }