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 }