github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelconfig.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"reflect"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/schema"
    11  	"github.com/juju/version/v2"
    12  
    13  	"github.com/juju/juju/controller"
    14  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    15  	"github.com/juju/juju/environs/config"
    16  )
    17  
    18  type attrValues map[string]interface{}
    19  
    20  var disallowedModelConfigAttrs = [...]string{
    21  	"admin-secret",
    22  	"ca-private-key",
    23  }
    24  
    25  // ModelConfig returns the complete config for the model
    26  func (m *Model) ModelConfig() (*config.Config, error) {
    27  	return getModelConfig(m.st.db(), m.UUID())
    28  }
    29  
    30  // AgentVersion returns the agent version for the model config.
    31  // If no agent version is found, it returns NotFound error.
    32  func (m *Model) AgentVersion() (version.Number, error) {
    33  	cfg, err := m.ModelConfig()
    34  	if err != nil {
    35  		return version.Number{}, errors.Trace(err)
    36  	}
    37  	ver, ok := cfg.AgentVersion()
    38  	if !ok {
    39  		return version.Number{}, errors.NotFoundf("agent version")
    40  	}
    41  	return ver, nil
    42  }
    43  
    44  func getModelConfig(db Database, uuid string) (*config.Config, error) {
    45  	modelSettings, err := readSettings(db, settingsC, modelGlobalKey)
    46  	if err != nil {
    47  		return nil, errors.Annotatef(err, "model %q", uuid)
    48  	}
    49  	return config.New(config.NoDefaults, modelSettings.Map())
    50  }
    51  
    52  // checkModelConfig returns an error if the config is definitely invalid.
    53  func checkModelConfig(cfg *config.Config) error {
    54  	allAttrs := cfg.AllAttrs()
    55  	for _, attr := range disallowedModelConfigAttrs {
    56  		if _, ok := allAttrs[attr]; ok {
    57  			return errors.Errorf(attr + " should never be written to the state")
    58  		}
    59  	}
    60  	if _, ok := cfg.AgentVersion(); !ok {
    61  		return errors.Errorf("agent-version must always be set in state")
    62  	}
    63  	for attr := range allAttrs {
    64  		if controller.ControllerOnlyAttribute(attr) {
    65  			return errors.Errorf("cannot set controller attribute %q on a model", attr)
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  // inheritedConfigAttributes returns the merged collection of inherited config
    72  // values used as model defaults when adding models or unsetting values.
    73  func (st *State) inheritedConfigAttributes() (map[string]interface{}, error) {
    74  	rspec, err := st.regionSpec()
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  	configSources := modelConfigSources(st, rspec)
    79  	values := make(attrValues)
    80  	for _, src := range configSources {
    81  		cfg, err := src.sourceFunc()
    82  		if errors.IsNotFound(err) {
    83  			continue
    84  		}
    85  		if err != nil {
    86  			return nil, errors.Annotatef(err, "reading %s settings", src.name)
    87  		}
    88  		for attrName, value := range cfg {
    89  			values[attrName] = value
    90  		}
    91  	}
    92  	return values, nil
    93  }
    94  
    95  // modelConfigValues returns the values and source for the supplied model config
    96  // when combined with controller and Juju defaults.
    97  func (model *Model) modelConfigValues(modelCfg attrValues) (config.ConfigValues, error) {
    98  	resultValues := make(attrValues)
    99  	for k, v := range modelCfg {
   100  		resultValues[k] = v
   101  	}
   102  
   103  	// Read all of the current inherited config values so
   104  	// we can dynamically reflect the origin of the model config.
   105  	rspec, err := model.st.regionSpec()
   106  	if err != nil {
   107  		return nil, errors.Trace(err)
   108  	}
   109  	configSources := modelConfigSources(model.st, rspec)
   110  	sourceNames := make([]string, 0, len(configSources))
   111  	sourceAttrs := make([]attrValues, 0, len(configSources))
   112  	for _, src := range configSources {
   113  		sourceNames = append(sourceNames, src.name)
   114  		cfg, err := src.sourceFunc()
   115  		if errors.IsNotFound(err) {
   116  			continue
   117  		}
   118  		if err != nil {
   119  			return nil, errors.Annotatef(err, "reading %s settings", src.name)
   120  		}
   121  		sourceAttrs = append(sourceAttrs, cfg)
   122  
   123  		// If no modelCfg was passed in, we'll accumulate data
   124  		// for the inherited values instead.
   125  		if len(modelCfg) == 0 {
   126  			for k, v := range cfg {
   127  				resultValues[k] = v
   128  			}
   129  		}
   130  	}
   131  
   132  	// Figure out the source of each config attribute based
   133  	// on the current model values and the inherited values.
   134  	result := make(config.ConfigValues)
   135  	for attr, val := range resultValues {
   136  		// Find the source of config for which the model
   137  		// value matches. If there's a match, the last match
   138  		// in the search order will be the source of config.
   139  		// If there's no match, the source is the model.
   140  		source := config.JujuModelConfigSource
   141  		n := len(sourceAttrs)
   142  		for i := range sourceAttrs {
   143  			// With the introduction of a slice for mode it makes it not
   144  			// possible to use equality check for slice types. We should fall
   145  			// back to the reflect.Deep equality to ensure we don't panic at
   146  			// runtime.
   147  			var equal bool
   148  			switch val.(type) {
   149  			case []interface{}:
   150  				equal = reflect.DeepEqual(sourceAttrs[n-i-1][attr], val)
   151  			default:
   152  				equal = sourceAttrs[n-i-1][attr] == val
   153  			}
   154  			if equal {
   155  				source = sourceNames[n-i-1]
   156  				break
   157  			}
   158  		}
   159  		result[attr] = config.ConfigValue{
   160  			Value:  val,
   161  			Source: source,
   162  		}
   163  	}
   164  	return result, nil
   165  }
   166  
   167  // UpdateModelConfigDefaultValues updates the inherited settings used when creating a new model.
   168  func (st *State) UpdateModelConfigDefaultValues(updateAttrs map[string]interface{}, removeAttrs []string, regionSpec *environscloudspec.CloudRegionSpec) error {
   169  	var key string
   170  
   171  	if regionSpec != nil {
   172  		if regionSpec.Region == "" {
   173  			key = cloudGlobalKey(regionSpec.Cloud)
   174  		} else {
   175  			key = regionSettingsGlobalKey(regionSpec.Cloud, regionSpec.Region)
   176  		}
   177  	} else {
   178  		// For backwards compatibility default to the model's cloud.
   179  		model, err := st.Model()
   180  		if err != nil {
   181  			return errors.Trace(err)
   182  		}
   183  		key = cloudGlobalKey(model.CloudName())
   184  	}
   185  	settings, err := readSettings(st.db(), globalSettingsC, key)
   186  	if err != nil {
   187  		if !errors.IsNotFound(err) {
   188  			return errors.Annotatef(err, "model %q", st.ModelUUID())
   189  		}
   190  		// We haven't created settings for this region yet.
   191  		_, err := createSettings(st.db(), globalSettingsC, key, updateAttrs)
   192  		if err != nil {
   193  			return errors.Annotatef(err, "model %q", st.ModelUUID())
   194  		}
   195  		return nil
   196  	}
   197  
   198  	// TODO(axw) 2013-12-6 #1167616
   199  	// Ensure that the settings on disk have not changed
   200  	// underneath us. The settings changes are actually
   201  	// applied as a delta to what's on disk; if there has
   202  	// been a concurrent update, the change may not be what
   203  	// the user asked for.
   204  
   205  	// Attempt to validate against the current old model and the new model, that
   206  	// should be enough to verify the config against.
   207  	// If there are additional fields in the config, then this should be fine
   208  	// and should not throw a validation error.
   209  	model, err := st.Model()
   210  	if err != nil {
   211  		return errors.Trace(err)
   212  	}
   213  	oldConfig, err := model.ModelConfig()
   214  	if err != nil {
   215  		return errors.Trace(err)
   216  	}
   217  	validCfg, err := st.buildAndValidateModelConfig(updateAttrs, removeAttrs, oldConfig)
   218  	if err != nil {
   219  		return errors.Trace(err)
   220  	}
   221  	validAttrs := validCfg.AllAttrs()
   222  	for k := range updateAttrs {
   223  		if v, ok := validAttrs[k]; ok {
   224  			updateAttrs[k] = v
   225  		}
   226  	}
   227  
   228  	updateAttrs = config.CoerceForStorage(updateAttrs)
   229  	settings.Update(updateAttrs)
   230  	for _, r := range removeAttrs {
   231  		settings.Delete(r)
   232  	}
   233  	_, err = settings.Write()
   234  	return err
   235  }
   236  
   237  // ModelConfigValues returns the config values for the model represented
   238  // by this state.
   239  func (model *Model) ModelConfigValues() (config.ConfigValues, error) {
   240  	cfg, err := model.ModelConfig()
   241  	if err != nil {
   242  		return nil, errors.Trace(err)
   243  	}
   244  	return model.modelConfigValues(cfg.AllAttrs())
   245  }
   246  
   247  // ModelConfigDefaultValues returns the default config values to be used
   248  // when creating a new model, and the origin of those values.
   249  func (st *State) ModelConfigDefaultValues(cloudName string) (config.ModelDefaultAttributes, error) {
   250  	cloud, err := st.Cloud(cloudName)
   251  	if err != nil {
   252  		return nil, errors.Trace(err)
   253  	}
   254  
   255  	result := make(config.ModelDefaultAttributes)
   256  	// Juju defaults
   257  	defaultAttrs, err := st.defaultInheritedConfig(cloudName)()
   258  	if err != nil {
   259  		return nil, errors.Trace(err)
   260  	}
   261  	for k, v := range defaultAttrs {
   262  		result[k] = config.AttributeDefaultValues{Default: v}
   263  	}
   264  	// Controller config
   265  	ciCfg, err := st.controllerInheritedConfig(cloudName)()
   266  	if err != nil && !errors.IsNotFound(err) {
   267  		return nil, errors.Trace(err)
   268  
   269  	}
   270  	for k, v := range ciCfg {
   271  		if ds, ok := result[k]; ok {
   272  			ds.Controller = v
   273  			result[k] = ds
   274  		} else {
   275  			result[k] = config.AttributeDefaultValues{Controller: v}
   276  		}
   277  	}
   278  	// Region config
   279  	for _, region := range cloud.Regions {
   280  		rspec := &environscloudspec.CloudRegionSpec{Cloud: cloudName, Region: region.Name}
   281  		riCfg, err := st.regionInheritedConfig(rspec)()
   282  		if err != nil {
   283  			if errors.IsNotFound(err) {
   284  				continue
   285  			}
   286  			return nil, errors.Trace(err)
   287  		}
   288  		for k, v := range riCfg {
   289  			regCfg := config.RegionDefaultValue{Name: region.Name, Value: v}
   290  			if ds, ok := result[k]; ok {
   291  				ds.Regions = append(result[k].Regions, regCfg)
   292  				result[k] = ds
   293  			} else {
   294  				result[k] = config.AttributeDefaultValues{Regions: []config.RegionDefaultValue{regCfg}}
   295  			}
   296  		}
   297  	}
   298  	return result, nil
   299  }
   300  
   301  // checkControllerInheritedConfig returns an error if the shared local cloud config is definitely invalid.
   302  func checkControllerInheritedConfig(attrs attrValues) error {
   303  	disallowedCloudConfigAttrs := append(disallowedModelConfigAttrs[:], config.AgentVersionKey)
   304  	for _, attr := range disallowedCloudConfigAttrs {
   305  		if _, ok := attrs[attr]; ok {
   306  			return errors.Errorf("local cloud config cannot contain " + attr)
   307  		}
   308  	}
   309  	for attrName := range attrs {
   310  		if controller.ControllerOnlyAttribute(attrName) {
   311  			return errors.Errorf("local cloud config cannot contain controller attribute %q", attrName)
   312  		}
   313  	}
   314  	return nil
   315  }
   316  
   317  func (st *State) buildAndValidateModelConfig(updateAttrs attrValues, removeAttrs []string, oldConfig *config.Config) (*config.Config, error) {
   318  	newConfig, err := oldConfig.Apply(updateAttrs)
   319  	if err != nil {
   320  		return nil, errors.Trace(err)
   321  	}
   322  	if len(removeAttrs) != 0 {
   323  		newConfig, err = newConfig.Remove(removeAttrs)
   324  		if err != nil {
   325  			return nil, errors.Trace(err)
   326  		}
   327  	}
   328  	if err := checkModelConfig(newConfig); err != nil {
   329  		return nil, errors.Trace(err)
   330  	}
   331  	return st.validate(newConfig, oldConfig)
   332  }
   333  
   334  type ValidateConfigFunc func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error
   335  
   336  // UpdateModelConfig adds, updates or removes attributes in the current
   337  // configuration of the model with the provided updateAttrs and
   338  // removeAttrs.
   339  func (m *Model) UpdateModelConfig(updateAttrs map[string]interface{}, removeAttrs []string, additionalValidation ...ValidateConfigFunc) error {
   340  	if len(updateAttrs)+len(removeAttrs) == 0 {
   341  		return nil
   342  	}
   343  
   344  	st := m.State()
   345  	if len(removeAttrs) > 0 {
   346  		var removed []string
   347  		if updateAttrs == nil {
   348  			updateAttrs = make(map[string]interface{})
   349  		}
   350  		// For each removed attribute, pick up any inherited value
   351  		// and if there's one, use that.
   352  		inherited, err := st.inheritedConfigAttributes()
   353  		if err != nil {
   354  			return errors.Trace(err)
   355  		}
   356  		for _, attr := range removeAttrs {
   357  			// We are updating an attribute, that takes
   358  			// precedence over removing.
   359  			if _, ok := updateAttrs[attr]; ok {
   360  				continue
   361  			}
   362  			if val, ok := inherited[attr]; ok {
   363  				updateAttrs[attr] = val
   364  			} else {
   365  				removed = append(removed, attr)
   366  			}
   367  		}
   368  		removeAttrs = removed
   369  	}
   370  	// TODO(axw) 2013-12-6 #1167616
   371  	// Ensure that the settings on disk have not changed
   372  	// underneath us. The settings changes are actually
   373  	// applied as a delta to what's on disk; if there has
   374  	// been a concurrent update, the change may not be what
   375  	// the user asked for.
   376  	modelSettings, err := readSettings(st.db(), settingsC, modelGlobalKey)
   377  	if err != nil {
   378  		return errors.Annotatef(err, "model %q", m.UUID())
   379  	}
   380  
   381  	oldConfig, err := m.ModelConfig()
   382  	if err != nil {
   383  		return errors.Trace(err)
   384  	}
   385  	for _, additionalValidationFunc := range additionalValidation {
   386  		err = additionalValidationFunc(updateAttrs, removeAttrs, oldConfig)
   387  		if err != nil {
   388  			return errors.Trace(err)
   389  		}
   390  	}
   391  	validCfg, err := st.buildAndValidateModelConfig(updateAttrs, removeAttrs, oldConfig)
   392  	if err != nil {
   393  		return errors.Trace(err)
   394  	}
   395  
   396  	validAttrs := validCfg.AllAttrs()
   397  	for k := range oldConfig.AllAttrs() {
   398  		if _, ok := validAttrs[k]; !ok {
   399  			modelSettings.Delete(k)
   400  		}
   401  	}
   402  	// Some values require marshalling before storage.
   403  	validAttrs = config.CoerceForStorage(validAttrs)
   404  
   405  	modelSettings.Update(validAttrs)
   406  	_, ops := modelSettings.settingsUpdateOps()
   407  	if len(ops) > 0 {
   408  		return modelSettings.write(ops)
   409  	}
   410  	return nil
   411  }
   412  
   413  type modelConfigSourceFunc func() (attrValues, error)
   414  
   415  type modelConfigSource struct {
   416  	name       string
   417  	sourceFunc modelConfigSourceFunc
   418  }
   419  
   420  // modelConfigSources returns a slice of named model config
   421  // sources, in hierarchical order. Starting from the first source,
   422  // config is retrieved and each subsequent source adds to the
   423  // overall config values, later values override earlier ones.
   424  func modelConfigSources(st *State, regionSpec *environscloudspec.CloudRegionSpec) []modelConfigSource {
   425  	return []modelConfigSource{
   426  		{config.JujuDefaultSource, st.defaultInheritedConfig(regionSpec.Cloud)},
   427  		{config.JujuControllerSource, st.controllerInheritedConfig(regionSpec.Cloud)},
   428  		{config.JujuRegionSource, st.regionInheritedConfig(regionSpec)},
   429  	}
   430  }
   431  
   432  // defaultInheritedConfig returns config values which are defined
   433  // as defaults in either Juju or the cloud's environ provider.
   434  func (st *State) defaultInheritedConfig(cloudName string) func() (attrValues, error) {
   435  	return func() (attrValues, error) {
   436  		var defaults = make(map[string]interface{})
   437  		for k, v := range config.ConfigDefaults() {
   438  			defaults[k] = v
   439  		}
   440  		providerDefaults, err := st.environsProviderConfigSchemaSource(cloudName)
   441  		if errors.IsNotImplemented(err) {
   442  			return defaults, nil
   443  		} else if err != nil {
   444  			return nil, errors.Trace(err)
   445  		}
   446  		fields := schema.FieldMap(providerDefaults.ConfigSchema(), providerDefaults.ConfigDefaults())
   447  		if coercedAttrs, err := fields.Coerce(defaults, nil); err != nil {
   448  			return nil, errors.Trace(err)
   449  		} else {
   450  			for k, v := range coercedAttrs.(map[string]interface{}) {
   451  				defaults[k] = v
   452  			}
   453  		}
   454  		return defaults, nil
   455  	}
   456  }
   457  
   458  // controllerInheritedConfig returns the inherited config values
   459  // sourced from the local cloud config.
   460  func (st *State) controllerInheritedConfig(cloudName string) func() (attrValues, error) {
   461  	return func() (attrValues, error) {
   462  		settings, err := readSettings(st.db(), globalSettingsC, cloudGlobalKey(cloudName))
   463  		if err != nil {
   464  			return nil, errors.Annotatef(err, "controller %q", st.ControllerUUID())
   465  		}
   466  		return settings.Map(), nil
   467  	}
   468  }
   469  
   470  // regionInheritedConfig returns the configuration attributes for the region in
   471  // the cloud where the model is targeted.
   472  func (st *State) regionInheritedConfig(regionSpec *environscloudspec.CloudRegionSpec) func() (attrValues, error) {
   473  	if regionSpec == nil {
   474  		return func() (attrValues, error) {
   475  			return nil, errors.New(
   476  				"no environscloudspec.CloudRegionSpec provided")
   477  		}
   478  	}
   479  	if regionSpec.Region == "" {
   480  		// It is expected that not all clouds have regions. So return not found
   481  		// if there is not a region here.
   482  		return func() (attrValues, error) {
   483  			return nil, errors.NotFoundf("region")
   484  		}
   485  	}
   486  	return func() (attrValues, error) {
   487  		settings, err := readSettings(st.db(),
   488  			globalSettingsC,
   489  			regionSettingsGlobalKey(regionSpec.Cloud, regionSpec.Region),
   490  		)
   491  		if err != nil {
   492  			return nil, errors.Annotatef(err, "region %q on %q cloud", regionSpec.Region, regionSpec.Cloud)
   493  		}
   494  		return settings.Map(), nil
   495  	}
   496  }
   497  
   498  // regionSpec returns a suitable environscloudspec.CloudRegionSpec for use in
   499  // regionInheritedConfig.
   500  func (st *State) regionSpec() (*environscloudspec.CloudRegionSpec, error) {
   501  	model, err := st.Model()
   502  	if err != nil {
   503  		return nil, errors.Trace(err)
   504  	}
   505  	rspec := &environscloudspec.CloudRegionSpec{
   506  		Cloud:  model.CloudName(),
   507  		Region: model.CloudRegion(),
   508  	}
   509  	return rspec, nil
   510  }
   511  
   512  // composeModelConfigAttributes returns a set of model config settings composed from known
   513  // sources of default values overridden by model specific attributes.
   514  func composeModelConfigAttributes(
   515  	modelAttr attrValues, configSources ...modelConfigSource,
   516  ) (attrValues, error) {
   517  	resultAttrs := make(attrValues)
   518  
   519  	// Compose default settings from all known sources.
   520  	for _, source := range configSources {
   521  		newSettings, err := source.sourceFunc()
   522  		if errors.IsNotFound(err) {
   523  			continue
   524  		}
   525  		if err != nil {
   526  			return nil, errors.Annotatef(err, "reading %s settings", source.name)
   527  		}
   528  		for name, val := range newSettings {
   529  			resultAttrs[name] = val
   530  		}
   531  	}
   532  
   533  	// Merge in model specific settings.
   534  	for attr, val := range modelAttr {
   535  		resultAttrs[attr] = val
   536  	}
   537  
   538  	return resultAttrs, nil
   539  }
   540  
   541  // ComposeNewModelConfig returns a complete map of config attributes suitable for
   542  // creating a new model, by combining user specified values with system defaults.
   543  func (st *State) ComposeNewModelConfig(modelAttr map[string]interface{}, regionSpec *environscloudspec.CloudRegionSpec) (map[string]interface{}, error) {
   544  	configSources := modelConfigSources(st, regionSpec)
   545  	return composeModelConfigAttributes(modelAttr, configSources...)
   546  }