~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/controller/modelmanager/createmodel.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}