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 }