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  }