github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/version"
    13  
    14  	"github.com/juju/juju/environs"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/tools"
    17  )
    18  
    19  var (
    20  	logger = loggo.GetLogger("juju.controller.modelmanager")
    21  )
    22  
    23  // ModelConfigCreator provides a method of creating a new model config.
    24  //
    25  // The zero value of ModelConfigCreator is usable with the limitations
    26  // noted on each struct field.
    27  type ModelConfigCreator struct {
    28  	// Provider will be used to obtain EnvironProviders for preparing
    29  	// and validating configuration.
    30  	Provider func(string) (environs.EnvironProvider, error)
    31  
    32  	// FindTools, if non-nil, will be used to validate the agent-version
    33  	// value in NewModelConfig if it differs from the base configuration.
    34  	//
    35  	// If FindTools is nil, agent-version may not be different to the
    36  	// base configuration.
    37  	FindTools func(version.Number) (tools.List, error)
    38  }
    39  
    40  // NewModelConfig returns a new model config given a base (controller) config
    41  // and a set of attributes that will be specific to the new model, overriding
    42  // any non-restricted attributes in the base configuration. The resulting
    43  // config will be suitable for creating a new model in state.
    44  //
    45  // If "attrs" does not include a UUID, a new, random one will be generated
    46  // and added to the config.
    47  //
    48  // The config will be validated with the provider before being returned.
    49  func (c ModelConfigCreator) NewModelConfig(
    50  	cloud environs.CloudSpec,
    51  	base *config.Config,
    52  	attrs map[string]interface{},
    53  ) (*config.Config, error) {
    54  
    55  	if err := c.checkVersion(base, attrs); err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  	provider, err := c.Provider(cloud.Type)
    59  	if err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  
    63  	// Generate a new UUID for the model as necessary,
    64  	// and finalize the new config.
    65  	if _, ok := attrs[config.UUIDKey]; !ok {
    66  		uuid, err := utils.NewUUID()
    67  		if err != nil {
    68  			return nil, errors.Trace(err)
    69  		}
    70  		attrs[config.UUIDKey] = uuid.String()
    71  	}
    72  	attrs[config.TypeKey] = cloud.Type
    73  	cfg, err := finalizeConfig(provider, cloud, attrs)
    74  	if err != nil {
    75  		return nil, errors.Trace(err)
    76  	}
    77  	return cfg, nil
    78  }
    79  
    80  func (c *ModelConfigCreator) checkVersion(base *config.Config, attrs map[string]interface{}) error {
    81  	baseVersion, ok := base.AgentVersion()
    82  	if !ok {
    83  		return errors.Errorf("agent-version not found in base config")
    84  	}
    85  
    86  	// If there is no agent-version specified, use the current version.
    87  	// otherwise we need to check for tools
    88  	value, ok := attrs["agent-version"]
    89  	if !ok {
    90  		attrs["agent-version"] = baseVersion.String()
    91  		return nil
    92  	}
    93  	versionStr, ok := value.(string)
    94  	if !ok {
    95  		return errors.Errorf("agent-version must be a string but has type '%T'", value)
    96  	}
    97  	versionNumber, err := version.Parse(versionStr)
    98  	if err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  
   102  	n := versionNumber.Compare(baseVersion)
   103  	switch {
   104  	case n > 0:
   105  		return errors.Errorf(
   106  			"agent-version (%s) cannot be greater than the controller (%s)",
   107  			versionNumber, baseVersion,
   108  		)
   109  	case n == 0:
   110  		// If the version is the same as the base config,
   111  		// then assume tools are available.
   112  		return nil
   113  	case n < 0:
   114  		if c.FindTools == nil {
   115  			return errors.New(
   116  				"agent-version does not match base config, " +
   117  					"and no tools-finder is supplied",
   118  			)
   119  		}
   120  	}
   121  
   122  	// Look to see if we have tools available for that version.
   123  	list, err := c.FindTools(versionNumber)
   124  	if err != nil {
   125  		return errors.Trace(err)
   126  	}
   127  	if len(list) == 0 {
   128  		return errors.Errorf("no agent binaries found for version %s", versionNumber)
   129  	}
   130  	logger.Tracef("found agent binaries: %#v", list)
   131  	return nil
   132  }
   133  
   134  // finalizeConfig creates the config object from attributes,
   135  // and calls EnvironProvider.PrepareConfig.
   136  func finalizeConfig(
   137  	provider environs.EnvironProvider,
   138  	cloud environs.CloudSpec,
   139  	attrs map[string]interface{},
   140  ) (*config.Config, error) {
   141  	cfg, err := config.New(config.UseDefaults, attrs)
   142  	if err != nil {
   143  		return nil, errors.Annotate(err, "creating config from values failed")
   144  	}
   145  	cfg, err = provider.PrepareConfig(environs.PrepareConfigParams{
   146  		Cloud:  cloud,
   147  		Config: cfg,
   148  	})
   149  	if err != nil {
   150  		return nil, errors.Annotate(err, "provider config preparation failed")
   151  	}
   152  	cfg, err = provider.Validate(cfg, nil)
   153  	if err != nil {
   154  		return nil, errors.Annotate(err, "provider config validation failed")
   155  	}
   156  	return cfg, nil
   157  }