1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
// Package modelmanager provides the business logic for
5
// model management operations in the controller.
11
"github.com/juju/errors"
12
"github.com/juju/loggo"
13
"github.com/juju/utils"
14
"github.com/juju/version"
16
"github.com/juju/juju/environs"
17
"github.com/juju/juju/environs/config"
18
"github.com/juju/juju/tools"
22
logger = loggo.GetLogger("juju.controller.modelmanager")
24
configValuesFromController = []string{
29
config.ControllerUUIDKey,
34
// IsAdmin is used when generating a model config for an admin user.
37
// IsNotAdmin is used when generating a model config for a non admin user.
41
// ModelConfigCreator provides a method of creating a new model config.
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.
49
// If FindTools is nil, agent-version may not be different to the
50
// base configuration.
51
FindTools func(version.Number) (tools.List, error)
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.
59
// If "attrs" does not include a UUID, a new, random one will be generated
60
// and added to the config.
62
// The config will be validated with the provider before being returned.
63
func (c ModelConfigCreator) NewModelConfig(
66
attrs map[string]interface{},
67
) (*config.Config, error) {
69
if err := c.checkVersion(base, attrs); err != nil {
70
return nil, errors.Trace(err)
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())
82
return nil, errors.Trace(err)
84
for _, field := range restrictedFields {
85
if _, ok := attrs[field]; !ok {
86
if baseValue, ok := baseAttrs[field]; ok {
87
attrs[field] = baseValue
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()
97
return nil, errors.Trace(err)
99
attrs[config.UUIDKey] = uuid.String()
101
cfg, err := finalizeConfig(isAdmin, base, attrs)
103
return nil, errors.Trace(err)
105
attrs = cfg.AllAttrs()
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)
123
func (c *ModelConfigCreator) checkVersion(base *config.Config, attrs map[string]interface{}) error {
124
baseVersion, ok := base.AgentVersion()
126
return errors.Errorf("agent-version not found in base config")
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"]
133
attrs["agent-version"] = baseVersion.String()
136
versionStr, ok := value.(string)
138
return errors.Errorf("agent-version must be a string but has type '%T'", value)
140
versionNumber, err := version.Parse(versionStr)
142
return errors.Trace(err)
145
n := versionNumber.Compare(baseVersion)
148
return errors.Errorf(
149
"agent-version (%s) cannot be greater than the controller (%s)",
150
versionNumber, baseVersion,
153
// If the version is the same as the base config,
154
// then assume tools are available.
157
if c.FindTools == nil {
159
"agent-version does not match base config, " +
160
"and no tools-finder is supplied",
165
// Look to see if we have tools available for that version.
166
list, err := c.FindTools(versionNumber)
168
return errors.Trace(err)
171
return errors.Errorf("no tools found for version %s", versionNumber)
173
logger.Tracef("found tools: %#v", list)
177
// RestrictedProviderFields returns the set of config fields that may not be
179
func RestrictedProviderFields(providerType string) ([]string, error) {
180
provider, err := environs.Provider(providerType)
182
return nil, errors.Trace(err)
185
fields = append(fields, configValuesFromController...)
186
fields = append(fields, provider.RestrictedConfigAttributes()...)
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())
196
return nil, errors.Trace(err)
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.
202
maybeCopyControllerSecrets(provider, controllerCfg.AllAttrs(), attrs)
204
cfg, err := config.New(config.UseDefaults, attrs)
206
return nil, errors.Annotate(err, "creating config from values failed")
209
cfg, err = provider.PrepareForCreateEnvironment(cfg)
211
return nil, errors.Trace(err)
213
cfg, err = provider.Validate(cfg, nil)
215
return nil, errors.Annotate(err, "provider validation failed")
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])
235
controllerCredentialAttrNames = append(controllerCredentialAttrNames, attrName)
237
// readFile is not needed server side.
238
readFile := func(string) ([]byte, error) {
239
return nil, errors.NotImplementedf("read file")
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 {
246
finalValues, err := schema.Finalize(possibleCredentialValues, readFile)
248
for k, v := range finalValues {
251
controllerCredentialAttrNames = nil
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]
262
for _, attrName := range controllerCredentialAttrNames {
263
if _, ok := attrs[attrName]; !ok {
264
attrs[attrName] = controllerAttrs[attrName]