github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/controller/modelmanager/createmodel.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package modelmanager provides the business logic for
     5  // model management operations in the controller.
     6  package modelmanager
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/utils"
    14  	"github.com/juju/version"
    15  
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/tools"
    19  )
    20  
    21  var (
    22  	logger = loggo.GetLogger("juju.controller.modelmanager")
    23  
    24  	configValuesFromController = []string{
    25  		"type",
    26  		config.CACertKey,
    27  		"state-port",
    28  		"api-port",
    29  		config.ControllerUUIDKey,
    30  	}
    31  )
    32  
    33  const (
    34  	// IsAdmin is used when generating a model config for an admin user.
    35  	IsAdmin = true
    36  
    37  	// IsNotAdmin is used when generating a model config for a non admin user.
    38  	IsNotAdmin = false
    39  )
    40  
    41  // ModelConfigCreator provides a method of creating a new model config.
    42  //
    43  // The zero value of ModelConfigCreator is usable with the limitations
    44  // noted on each struct field.
    45  type ModelConfigCreator struct {
    46  	// FindTools, if non-nil, will be used to validate the agent-version
    47  	// value in NewModelConfig if it differs from the base configuration.
    48  	//
    49  	// If FindTools is nil, agent-version may not be different to the
    50  	// base configuration.
    51  	FindTools func(version.Number) (tools.List, error)
    52  }
    53  
    54  // NewModelConfig returns a new model config given a base (controller) config
    55  // and a set of attributes that will be specific to the new model, overriding
    56  // any non-restricted attributes in the base configuration. The resulting
    57  // config will be suitable for creating a new model in state.
    58  //
    59  // If "attrs" does not include a UUID, a new, random one will be generated
    60  // and added to the config.
    61  //
    62  // The config will be validated with the provider before being returned.
    63  func (c ModelConfigCreator) NewModelConfig(
    64  	isAdmin bool,
    65  	base *config.Config,
    66  	attrs map[string]interface{},
    67  ) (*config.Config, error) {
    68  
    69  	if err := c.checkVersion(base, attrs); err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  
    73  	// Before comparing any values, we need to push the config through
    74  	// the provider validation code. One of the reasons for this is that
    75  	// numbers being serialized through JSON get turned into float64. The
    76  	// schema code used in config will convert these back into integers.
    77  	// However, before we can create a valid config, we need to make sure
    78  	// we copy across fields from the main config that aren't there.
    79  	baseAttrs := base.AllAttrs()
    80  	restrictedFields, err := RestrictedProviderFields(base.Type())
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	for _, field := range restrictedFields {
    85  		if _, ok := attrs[field]; !ok {
    86  			if baseValue, ok := baseAttrs[field]; ok {
    87  				attrs[field] = baseValue
    88  			}
    89  		}
    90  	}
    91  
    92  	// Generate a new UUID for the model as necessary,
    93  	// and finalize the new config.
    94  	if _, ok := attrs[config.UUIDKey]; !ok {
    95  		uuid, err := utils.NewUUID()
    96  		if err != nil {
    97  			return nil, errors.Trace(err)
    98  		}
    99  		attrs[config.UUIDKey] = uuid.String()
   100  	}
   101  	cfg, err := finalizeConfig(isAdmin, base, attrs)
   102  	if err != nil {
   103  		return nil, errors.Trace(err)
   104  	}
   105  	attrs = cfg.AllAttrs()
   106  
   107  	// Any values that would normally be copied from the controller
   108  	// config can also be defined, but if they differ from the controller
   109  	// values, an error is returned.
   110  	for _, field := range restrictedFields {
   111  		if value, ok := attrs[field]; ok {
   112  			if serverValue := baseAttrs[field]; value != serverValue {
   113  				return nil, errors.Errorf(
   114  					"specified %s \"%v\" does not match controller \"%v\"",
   115  					field, value, serverValue)
   116  			}
   117  		}
   118  	}
   119  
   120  	return cfg, nil
   121  }
   122  
   123  func (c *ModelConfigCreator) checkVersion(base *config.Config, attrs map[string]interface{}) error {
   124  	baseVersion, ok := base.AgentVersion()
   125  	if !ok {
   126  		return errors.Errorf("agent-version not found in base config")
   127  	}
   128  
   129  	// If there is no agent-version specified, use the current version.
   130  	// otherwise we need to check for tools
   131  	value, ok := attrs["agent-version"]
   132  	if !ok {
   133  		attrs["agent-version"] = baseVersion.String()
   134  		return nil
   135  	}
   136  	versionStr, ok := value.(string)
   137  	if !ok {
   138  		return errors.Errorf("agent-version must be a string but has type '%T'", value)
   139  	}
   140  	versionNumber, err := version.Parse(versionStr)
   141  	if err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  
   145  	n := versionNumber.Compare(baseVersion)
   146  	switch {
   147  	case n > 0:
   148  		return errors.Errorf(
   149  			"agent-version (%s) cannot be greater than the controller (%s)",
   150  			versionNumber, baseVersion,
   151  		)
   152  	case n == 0:
   153  		// If the version is the same as the base config,
   154  		// then assume tools are available.
   155  		return nil
   156  	case n < 0:
   157  		if c.FindTools == nil {
   158  			return errors.New(
   159  				"agent-version does not match base config, " +
   160  					"and no tools-finder is supplied",
   161  			)
   162  		}
   163  	}
   164  
   165  	// Look to see if we have tools available for that version.
   166  	list, err := c.FindTools(versionNumber)
   167  	if err != nil {
   168  		return errors.Trace(err)
   169  	}
   170  	if len(list) == 0 {
   171  		return errors.Errorf("no tools found for version %s", versionNumber)
   172  	}
   173  	logger.Tracef("found tools: %#v", list)
   174  	return nil
   175  }
   176  
   177  // RestrictedProviderFields returns the set of config fields that may not be
   178  // overridden.
   179  func RestrictedProviderFields(providerType string) ([]string, error) {
   180  	provider, err := environs.Provider(providerType)
   181  	if err != nil {
   182  		return nil, errors.Trace(err)
   183  	}
   184  	var fields []string
   185  	fields = append(fields, configValuesFromController...)
   186  	fields = append(fields, provider.RestrictedConfigAttributes()...)
   187  	return fields, nil
   188  }
   189  
   190  // finalizeConfig creates the config object from attributes, calls
   191  // PrepareForCreateEnvironment, and then finally validates the config
   192  // before returning it.
   193  func finalizeConfig(isAdmin bool, controllerCfg *config.Config, attrs map[string]interface{}) (*config.Config, error) {
   194  	provider, err := environs.Provider(controllerCfg.Type())
   195  	if err != nil {
   196  		return nil, errors.Trace(err)
   197  	}
   198  
   199  	// Controller admins creating models do not have to re-supply new secrets.
   200  	// These may be copied from the controller model if not supplied.
   201  	if isAdmin {
   202  		maybeCopyControllerSecrets(provider, controllerCfg.AllAttrs(), attrs)
   203  	}
   204  	cfg, err := config.New(config.UseDefaults, attrs)
   205  	if err != nil {
   206  		return nil, errors.Annotate(err, "creating config from values failed")
   207  	}
   208  
   209  	cfg, err = provider.PrepareForCreateEnvironment(cfg)
   210  	if err != nil {
   211  		return nil, errors.Trace(err)
   212  	}
   213  	cfg, err = provider.Validate(cfg, nil)
   214  	if err != nil {
   215  		return nil, errors.Annotate(err, "provider validation failed")
   216  	}
   217  	return cfg, nil
   218  }
   219  
   220  // maybeCopyControllerSecrets asks the specified provider for all possible config
   221  // attributes representing credential values and copies those across from the
   222  // controller config into the new model's config attrs if not already present.
   223  func maybeCopyControllerSecrets(provider environs.ProviderCredentials, controllerAttrs, attrs map[string]interface{}) {
   224  	requiredControllerAttrNames := []string{"authorized-keys"}
   225  	var controllerCredentialAttrNames []string
   226  	for _, schema := range provider.CredentialSchemas() {
   227  		// possibleCredentialValues holds any values from attrs that belong to
   228  		// the credential schema.
   229  		possibleCredentialValues := make(map[string]string)
   230  		for _, attr := range schema {
   231  			attrName := attr.Name
   232  			if v, ok := attrs[attrName]; ok && v != "" {
   233  				possibleCredentialValues[attrName] = fmt.Sprintf("%v", attrs[attrName])
   234  			}
   235  			controllerCredentialAttrNames = append(controllerCredentialAttrNames, attrName)
   236  		}
   237  		// readFile is not needed server side.
   238  		readFile := func(string) ([]byte, error) {
   239  			return nil, errors.NotImplementedf("read file")
   240  		}
   241  		// If the user has passed in valid credentials, we'll use
   242  		// those and not the ones from the controller.
   243  		if len(possibleCredentialValues) == 0 {
   244  			continue
   245  		}
   246  		finalValues, err := schema.Finalize(possibleCredentialValues, readFile)
   247  		if err == nil {
   248  			for k, v := range finalValues {
   249  				attrs[k] = v
   250  			}
   251  			controllerCredentialAttrNames = nil
   252  			break
   253  		}
   254  	}
   255  
   256  	// Ensure any required attributes which are empty are copied from the controller config.
   257  	for _, attrName := range requiredControllerAttrNames {
   258  		if _, ok := attrs[attrName]; !ok {
   259  			attrs[attrName] = controllerAttrs[attrName]
   260  		}
   261  	}
   262  	for _, attrName := range controllerCredentialAttrNames {
   263  		if _, ok := attrs[attrName]; !ok {
   264  			attrs[attrName] = controllerAttrs[attrName]
   265  		}
   266  	}
   267  }