github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/azure/config.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "net/url" 8 "strings" 9 10 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/azure" 11 "github.com/Azure/azure-sdk-for-go/arm/storage" 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 "github.com/juju/schema" 15 16 "github.com/juju/juju/environs/config" 17 ) 18 19 const ( 20 configAttrAppId = "application-id" 21 configAttrSubscriptionId = "subscription-id" 22 configAttrTenantId = "tenant-id" 23 configAttrAppPassword = "application-password" 24 configAttrLocation = "location" 25 configAttrEndpoint = "endpoint" 26 configAttrStorageEndpoint = "storage-endpoint" 27 configAttrStorageAccountType = "storage-account-type" 28 29 // The below bits are internal book-keeping things, rather than 30 // configuration. Config is just what we have to work with. 31 32 // configAttrStorageAccount is the name of the storage account. We 33 // can't just use a well-defined name for the storage acocunt because 34 // storage account names must be globally unique; each storage account 35 // has an associated public DNS entry. 36 configAttrStorageAccount = "storage-account" 37 38 // configAttrStorageAccountKey is the primary key for the storage 39 // account. 40 configAttrStorageAccountKey = "storage-account-key" 41 42 // configAttrControllerResourceGroup is the resource group 43 // corresponding to the controller environment. Each environment needs 44 // to know this because some resources are shared, and live in the 45 // controller environment's resource group. 46 configAttrControllerResourceGroup = "controller-resource-group" 47 48 // resourceNameLengthMax is the maximum length of resource 49 // names in Azure. 50 resourceNameLengthMax = 80 51 ) 52 53 var configFields = schema.Fields{ 54 configAttrLocation: schema.String(), 55 configAttrEndpoint: schema.String(), 56 configAttrStorageEndpoint: schema.String(), 57 configAttrAppId: schema.String(), 58 configAttrSubscriptionId: schema.String(), 59 configAttrTenantId: schema.String(), 60 configAttrAppPassword: schema.String(), 61 configAttrStorageAccount: schema.String(), 62 configAttrStorageAccountKey: schema.String(), 63 configAttrStorageAccountType: schema.String(), 64 configAttrControllerResourceGroup: schema.String(), 65 } 66 67 var configDefaults = schema.Defaults{ 68 configAttrStorageAccount: schema.Omit, 69 configAttrStorageAccountKey: schema.Omit, 70 configAttrControllerResourceGroup: schema.Omit, 71 configAttrStorageAccountType: string(storage.StandardLRS), 72 } 73 74 var requiredConfigAttributes = []string{ 75 configAttrAppId, 76 configAttrAppPassword, 77 configAttrSubscriptionId, 78 configAttrTenantId, 79 configAttrLocation, 80 configAttrEndpoint, 81 configAttrStorageEndpoint, 82 configAttrControllerResourceGroup, 83 } 84 85 var immutableConfigAttributes = []string{ 86 configAttrSubscriptionId, 87 configAttrTenantId, 88 configAttrControllerResourceGroup, 89 configAttrStorageAccount, 90 configAttrStorageAccountType, 91 } 92 93 var internalConfigAttributes = []string{ 94 configAttrStorageAccount, 95 configAttrStorageAccountKey, 96 configAttrControllerResourceGroup, 97 } 98 99 type azureModelConfig struct { 100 *config.Config 101 token *azure.ServicePrincipalToken 102 subscriptionId string 103 location string // canonicalized 104 endpoint string 105 storageEndpoint string 106 storageAccount string 107 storageAccountKey string 108 storageAccountType storage.AccountType 109 controllerResourceGroup string 110 } 111 112 var knownStorageAccountTypes = []string{ 113 "Standard_LRS", "Standard_GRS", "Standard_RAGRS", "Standard_ZRS", "Premium_LRS", 114 } 115 116 // Validate ensures that the provided configuration is valid for this 117 // provider, and that changes between the old (if provided) and new 118 // configurations are valid. 119 func (*azureEnvironProvider) Validate(newCfg, oldCfg *config.Config) (*config.Config, error) { 120 _, err := validateConfig(newCfg, oldCfg) 121 if err != nil { 122 return nil, errors.Trace(err) 123 } 124 return newCfg, nil 125 } 126 127 func validateConfig(newCfg, oldCfg *config.Config) (*azureModelConfig, error) { 128 err := config.Validate(newCfg, oldCfg) 129 if err != nil { 130 return nil, err 131 } 132 133 validated, err := newCfg.ValidateUnknownAttrs(configFields, configDefaults) 134 if err != nil { 135 return nil, err 136 } 137 138 // Ensure required configuration is provided. 139 for _, key := range requiredConfigAttributes { 140 if value, ok := validated[key].(string); !ok || value == "" { 141 return nil, errors.Errorf("%q config not specified", key) 142 } 143 } 144 if oldCfg != nil { 145 // Ensure immutable configuration isn't changed. 146 oldUnknownAttrs := oldCfg.UnknownAttrs() 147 for _, key := range immutableConfigAttributes { 148 oldValue, hadValue := oldUnknownAttrs[key].(string) 149 if hadValue { 150 newValue, haveValue := validated[key].(string) 151 if !haveValue { 152 return nil, errors.Errorf( 153 "cannot remove immutable %q config", key, 154 ) 155 } 156 if newValue != oldValue { 157 return nil, errors.Errorf( 158 "cannot change immutable %q config (%v -> %v)", 159 key, oldValue, newValue, 160 ) 161 } 162 } 163 // It's valid to go from not having to having. 164 } 165 // TODO(axw) figure out how we intend to handle changing 166 // secrets, such as application key 167 } 168 169 // Resource group names must not exceed 80 characters. Resource group 170 // names are based on the model UUID and model name, the latter of 171 // which the model creator controls. 172 modelTag := names.NewModelTag(newCfg.UUID()) 173 resourceGroup := resourceGroupName(modelTag, newCfg.Name()) 174 if n := len(resourceGroup); n > resourceNameLengthMax { 175 smallestResourceGroup := resourceGroupName(modelTag, "") 176 return nil, errors.Errorf(`resource group name %q is too long 177 178 Please choose a model name of no more than %d characters.`, 179 resourceGroup, 180 resourceNameLengthMax-len(smallestResourceGroup), 181 ) 182 } 183 184 location := canonicalLocation(validated[configAttrLocation].(string)) 185 endpoint := validated[configAttrEndpoint].(string) 186 storageEndpoint := validated[configAttrStorageEndpoint].(string) 187 appId := validated[configAttrAppId].(string) 188 subscriptionId := validated[configAttrSubscriptionId].(string) 189 tenantId := validated[configAttrTenantId].(string) 190 appPassword := validated[configAttrAppPassword].(string) 191 storageAccount, _ := validated[configAttrStorageAccount].(string) 192 storageAccountKey, _ := validated[configAttrStorageAccountKey].(string) 193 storageAccountType := validated[configAttrStorageAccountType].(string) 194 controllerResourceGroup := validated[configAttrControllerResourceGroup].(string) 195 196 if newCfg.FirewallMode() == config.FwGlobal { 197 // We do not currently support the "global" firewall mode. 198 return nil, errNoFwGlobal 199 } 200 201 if !isKnownStorageAccountType(storageAccountType) { 202 return nil, errors.Errorf( 203 "invalid storage account type %q, expected one of: %q", 204 storageAccountType, knownStorageAccountTypes, 205 ) 206 } 207 208 // The Azure storage code wants the endpoint host only, not the URL. 209 storageEndpointURL, err := url.Parse(storageEndpoint) 210 if err != nil { 211 return nil, errors.Annotate(err, "parsing storage endpoint URL") 212 } 213 214 token, err := azure.NewServicePrincipalToken( 215 appId, appPassword, tenantId, 216 azure.AzureResourceManagerScope, 217 ) 218 if err != nil { 219 return nil, errors.Annotate(err, "constructing service principal token") 220 } 221 222 azureConfig := &azureModelConfig{ 223 newCfg, 224 token, 225 subscriptionId, 226 location, 227 endpoint, 228 storageEndpointURL.Host, 229 storageAccount, 230 storageAccountKey, 231 storage.AccountType(storageAccountType), 232 controllerResourceGroup, 233 } 234 235 return azureConfig, nil 236 } 237 238 // isKnownStorageAccountType reports whether or not the given string identifies 239 // a known storage account type. 240 func isKnownStorageAccountType(t string) bool { 241 for _, knownStorageAccountType := range knownStorageAccountTypes { 242 if t == knownStorageAccountType { 243 return true 244 } 245 } 246 return false 247 } 248 249 // canonicalLocation returns the canonicalized location string. This involves 250 // stripping whitespace, and lowercasing. The ARM APIs do not support embedded 251 // whitespace, whereas the old Service Management APIs used to; we allow the 252 // user to provide either, and canonicalize them to one form that ARM allows. 253 func canonicalLocation(s string) string { 254 s = strings.Replace(s, " ", "", -1) 255 return strings.ToLower(s) 256 }