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 }