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