github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "fmt" 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 "github.com/juju/utils" 16 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 type joyentProvider struct{} 25 26 var providerInstance = joyentProvider{} 27 var _ environs.EnvironProvider = providerInstance 28 29 var _ simplestreams.HasRegion = (*joyentEnviron)(nil) 30 31 var errNotImplemented = errors.New("not implemented in Joyent provider") 32 33 // RestrictedConfigAttributes is specified in the EnvironProvider interface. 34 func (joyentProvider) RestrictedConfigAttributes() []string { 35 return nil 36 } 37 38 // PrepareForCreateEnvironment is specified in the EnvironProvider interface. 39 func (joyentProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) { 40 // Turn an incomplete config into a valid one, if possible. 41 attrs := cfg.UnknownAttrs() 42 43 if _, ok := attrs["control-dir"]; !ok { 44 uuid, err := utils.NewUUID() 45 if err != nil { 46 return nil, errors.Trace(err) 47 } 48 attrs["control-dir"] = fmt.Sprintf("%x", uuid.Raw()) 49 } 50 return cfg.Apply(attrs) 51 } 52 53 func (p joyentProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { 54 cfg, err := p.PrepareForCreateEnvironment(cfg) 55 if err != nil { 56 return nil, errors.Trace(err) 57 } 58 e, err := p.Open(cfg) 59 if err != nil { 60 return nil, errors.Trace(err) 61 } 62 if ctx.ShouldVerifyCredentials() { 63 if err := verifyCredentials(e.(*joyentEnviron)); err != nil { 64 return nil, errors.Trace(err) 65 } 66 } 67 return e, nil 68 } 69 70 const unauthorisedMessage = ` 71 Please ensure the Manta username and SSH access key you have 72 specified are correct. You can create or import an SSH key via 73 the "Account Summary" 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.Ecfg()) 81 if err != nil { 82 return err 83 } 84 httpClient := client.NewClient(e.Ecfg().sdcUrl(), 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(cfg *environConfig) (*auth.Credentials, error) { 98 authentication, err := auth.NewAuth(cfg.mantaUser(), cfg.privateKey(), cfg.algorithm()) 99 if err != nil { 100 return nil, errors.Errorf("cannot create credentials: %v", err) 101 } 102 return &auth.Credentials{ 103 UserAuthentication: authentication, 104 MantaKeyId: cfg.mantaKeyId(), 105 MantaEndpoint: auth.Endpoint{URL: cfg.mantaUrl()}, 106 SdcKeyId: cfg.sdcKeyId(), 107 SdcEndpoint: auth.Endpoint{URL: cfg.sdcUrl()}, 108 }, nil 109 } 110 111 func (joyentProvider) Open(cfg *config.Config) (environs.Environ, error) { 112 env, err := newEnviron(cfg) 113 if err != nil { 114 return nil, err 115 } 116 return env, nil 117 } 118 119 func (joyentProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 120 newEcfg, err := validateConfig(cfg, old) 121 if err != nil { 122 return nil, errors.Errorf("invalid Joyent provider config: %v", err) 123 } 124 return cfg.Apply(newEcfg.attrs) 125 } 126 127 func (joyentProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { 128 // If you keep configSecretFields up to date, this method should Just Work. 129 ecfg, err := validateConfig(cfg, nil) 130 if err != nil { 131 return nil, err 132 } 133 secretAttrs := map[string]string{} 134 for _, field := range configSecretFields { 135 if value, ok := ecfg.attrs[field]; ok { 136 if stringValue, ok := value.(string); ok { 137 secretAttrs[field] = stringValue 138 } else { 139 // All your secret attributes must be strings at the moment. Sorry. 140 // It's an expedient and hopefully temporary measure that helps us 141 // plug a security hole in the API. 142 return nil, errors.Errorf( 143 "secret %q field must have a string value; got %v", 144 field, value, 145 ) 146 } 147 } 148 } 149 return secretAttrs, nil 150 } 151 152 func (joyentProvider) BoilerplateConfig() string { 153 return boilerplateConfig 154 155 } 156 157 func GetProviderInstance() environs.EnvironProvider { 158 return providerInstance 159 } 160 161 // MetadataLookupParams returns parameters which are used to query image metadata to 162 // find matching image information. 163 func (p joyentProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 164 if region == "" { 165 return nil, errors.Errorf("region must be specified") 166 } 167 return &simplestreams.MetadataLookupParams{ 168 Region: region, 169 Architectures: []string{"amd64", "armhf"}, 170 }, nil 171 } 172 173 func (p joyentProvider) newConfig(cfg *config.Config) (*environConfig, error) { 174 valid, err := p.Validate(cfg, nil) 175 if err != nil { 176 return nil, err 177 } 178 return &environConfig{valid, valid.UnknownAttrs()}, nil 179 }