github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/joyent/config.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 "io/ioutil" 9 "net/url" 10 "os" 11 "strings" 12 13 "github.com/juju/errors" 14 "github.com/juju/schema" 15 "github.com/juju/utils" 16 17 "github.com/juju/juju/environs/config" 18 ) 19 20 // boilerplateConfig will be shown in help output, so please keep it up to 21 // date when you change environment configuration below. 22 const boilerplateConfig = `joyent: 23 type: joyent 24 25 # SDC config 26 # Can be set via env variables, or specified here 27 # sdc-user: <secret> 28 # Can be set via env variables, or specified here 29 # sdc-key-id: <secret> 30 # url defaults to us-west-1 DC, override if required 31 # sdc-url: https://us-west-1.api.joyentcloud.com 32 33 # Manta config 34 # Can be set via env variables, or specified here 35 # manta-user: <secret> 36 # Can be set via env variables, or specified here 37 # manta-key-id: <secret> 38 # url defaults to us-east DC, override if required 39 # manta-url: https://us-east.manta.joyent.com 40 41 # Auth config 42 # private-key-path is the private key used to sign Joyent requests. 43 # Alternatively, you can supply "private-key" with the content of the private 44 # key instead supplying the path to a file. 45 # private-key-path: ~/.ssh/foo_id 46 # algorithm defaults to rsa-sha256, override if required 47 # algorithm: rsa-sha256 48 49 # Whether or not to refresh the list of available updates for an 50 # OS. The default option of true is recommended for use in 51 # production systems, but disabling this can speed up local 52 # deployments for development or testing. 53 # 54 # enable-os-refresh-update: true 55 56 # Whether or not to perform OS upgrades when machines are 57 # provisioned. The default option of true is recommended for use 58 # in production systems, but disabling this can speed up local 59 # deployments for development or testing. 60 # 61 # enable-os-upgrade: true 62 63 ` 64 65 const ( 66 SdcAccount = "SDC_ACCOUNT" 67 SdcKeyId = "SDC_KEY_ID" 68 SdcUrl = "SDC_URL" 69 MantaUser = "MANTA_USER" 70 MantaKeyId = "MANTA_KEY_ID" 71 MantaUrl = "MANTA_URL" 72 MantaPrivateKeyFile = "MANTA_PRIVATE_KEY_FILE" 73 74 sdcUser = "sdc-user" 75 sdcKeyId = "sdc-key-id" 76 sdcUrl = "sdc-url" 77 mantaUser = "manta-user" 78 mantaKeyId = "manta-key-id" 79 mantaUrl = "manta-url" 80 privateKeyPath = "private-key-path" 81 algorithm = "algorithm" 82 controlDir = "control-dir" 83 privateKey = "private-key" 84 ) 85 86 var environmentVariables = map[string]string{ 87 sdcUser: SdcAccount, 88 sdcKeyId: SdcKeyId, 89 sdcUrl: SdcUrl, 90 mantaUser: MantaUser, 91 mantaKeyId: MantaKeyId, 92 mantaUrl: MantaUrl, 93 privateKeyPath: MantaPrivateKeyFile, 94 } 95 96 var configFields = schema.Fields{ 97 sdcUser: schema.String(), 98 sdcKeyId: schema.String(), 99 sdcUrl: schema.String(), 100 mantaUser: schema.String(), 101 mantaKeyId: schema.String(), 102 mantaUrl: schema.String(), 103 privateKeyPath: schema.String(), 104 algorithm: schema.String(), 105 controlDir: schema.String(), 106 privateKey: schema.String(), 107 } 108 109 var configDefaults = schema.Defaults{ 110 sdcUrl: "https://us-west-1.api.joyentcloud.com", 111 mantaUrl: "https://us-east.manta.joyent.com", 112 algorithm: "rsa-sha256", 113 privateKeyPath: schema.Omit, 114 sdcUser: schema.Omit, 115 sdcKeyId: schema.Omit, 116 mantaUser: schema.Omit, 117 mantaKeyId: schema.Omit, 118 privateKey: schema.Omit, 119 } 120 121 var requiredFields = []string{ 122 sdcUrl, 123 mantaUrl, 124 algorithm, 125 sdcUser, 126 sdcKeyId, 127 mantaUser, 128 mantaKeyId, 129 // privatekey and privatekeypath are handled separately 130 } 131 132 var configSecretFields = []string{ 133 sdcUser, 134 sdcKeyId, 135 mantaUser, 136 mantaKeyId, 137 privateKey, 138 } 139 140 var configImmutableFields = []string{ 141 sdcUrl, 142 mantaUrl, 143 privateKeyPath, 144 privateKey, 145 algorithm, 146 } 147 148 func validateConfig(cfg, old *config.Config) (*environConfig, error) { 149 // Check for valid changes for the base config values. 150 if err := config.Validate(cfg, old); err != nil { 151 return nil, err 152 } 153 154 newAttrs, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) 155 if err != nil { 156 return nil, err 157 } 158 envConfig := &environConfig{cfg, newAttrs} 159 // If an old config was supplied, check any immutable fields have not changed. 160 if old != nil { 161 oldEnvConfig, err := validateConfig(old, nil) 162 if err != nil { 163 return nil, err 164 } 165 for _, field := range configImmutableFields { 166 if oldEnvConfig.attrs[field] != envConfig.attrs[field] { 167 return nil, fmt.Errorf( 168 "%s: cannot change from %v to %v", 169 field, oldEnvConfig.attrs[field], envConfig.attrs[field], 170 ) 171 } 172 } 173 } 174 175 // Read env variables to fill in any missing fields. 176 for field, envVar := range environmentVariables { 177 // If field is not set, get it from env variables 178 if nilOrEmptyString(envConfig.attrs[field]) { 179 localEnvVariable := os.Getenv(envVar) 180 if localEnvVariable != "" { 181 envConfig.attrs[field] = localEnvVariable 182 } else { 183 if field != privateKeyPath { 184 return nil, fmt.Errorf("cannot get %s value from environment variable %s", field, envVar) 185 } 186 } 187 } 188 } 189 190 if err := ensurePrivateKeyOrPath(envConfig); err != nil { 191 return nil, err 192 } 193 194 // Now that we've ensured private-key-path is properly set, we go back and set 195 // up the private key - this is used to sign requests. 196 if nilOrEmptyString(envConfig.attrs[privateKey]) { 197 keyFile, err := utils.NormalizePath(envConfig.attrs[privateKeyPath].(string)) 198 if err != nil { 199 return nil, err 200 } 201 priv, err := ioutil.ReadFile(keyFile) 202 if err != nil { 203 return nil, err 204 } 205 envConfig.attrs[privateKey] = string(priv) 206 } 207 208 // Check for missing fields. 209 for _, field := range requiredFields { 210 if nilOrEmptyString(envConfig.attrs[field]) { 211 return nil, fmt.Errorf("%s: must not be empty", field) 212 } 213 } 214 return envConfig, nil 215 } 216 217 // Ensure private-key-path is set. 218 func ensurePrivateKeyOrPath(envConfig *environConfig) error { 219 if !nilOrEmptyString(envConfig.attrs[privateKeyPath]) { 220 return nil 221 } 222 if path := os.Getenv(environmentVariables[privateKeyPath]); path != "" { 223 envConfig.attrs[privateKeyPath] = path 224 return nil 225 } 226 if !nilOrEmptyString(envConfig.attrs[privateKey]) { 227 return nil 228 } 229 230 return errors.New("no ssh private key specified in joyent configuration") 231 } 232 233 type environConfig struct { 234 *config.Config 235 attrs map[string]interface{} 236 } 237 238 func (ecfg *environConfig) GetAttrs() map[string]interface{} { 239 return ecfg.attrs 240 } 241 242 func (ecfg *environConfig) sdcUrl() string { 243 return ecfg.attrs[sdcUrl].(string) 244 } 245 246 func (ecfg *environConfig) sdcUser() string { 247 return ecfg.attrs[sdcUser].(string) 248 } 249 250 func (ecfg *environConfig) sdcKeyId() string { 251 return ecfg.attrs[sdcKeyId].(string) 252 } 253 254 func (ecfg *environConfig) mantaUrl() string { 255 return ecfg.attrs[mantaUrl].(string) 256 } 257 258 func (ecfg *environConfig) mantaUser() string { 259 return ecfg.attrs[mantaUser].(string) 260 } 261 262 func (ecfg *environConfig) mantaKeyId() string { 263 return ecfg.attrs[mantaKeyId].(string) 264 } 265 266 func (ecfg *environConfig) privateKey() string { 267 if v, ok := ecfg.attrs[privateKey]; ok { 268 return v.(string) 269 } 270 return "" 271 } 272 273 func (ecfg *environConfig) algorithm() string { 274 return ecfg.attrs[algorithm].(string) 275 } 276 277 func (c *environConfig) controlDir() string { 278 return c.attrs[controlDir].(string) 279 } 280 281 func (c *environConfig) ControlDir() string { 282 return c.controlDir() 283 } 284 285 func (ecfg *environConfig) SdcUrl() string { 286 return ecfg.sdcUrl() 287 } 288 289 func (ecfg *environConfig) Region() string { 290 sdcUrl := ecfg.sdcUrl() 291 // Check if running against local services 292 if isLocalhost(sdcUrl) { 293 return "some-region" 294 } 295 return sdcUrl[strings.LastIndex(sdcUrl, "/")+1 : strings.Index(sdcUrl, ".")] 296 } 297 298 func isLocalhost(u string) bool { 299 parsedUrl, err := url.Parse(u) 300 if err != nil { 301 return false 302 } 303 if strings.HasPrefix(parsedUrl.Host, "localhost") || strings.HasPrefix(parsedUrl.Host, "127.0.0.") { 304 return true 305 } 306 307 return false 308 } 309 310 func nilOrEmptyString(i interface{}) bool { 311 return i == nil || i == "" 312 }