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