/
prepare.go
271 lines (239 loc) · 8.61 KB
/
prepare.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// Copyright 2011-2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package bootstrap
import (
"github.com/juju/errors"
"github.com/juju/featureflag"
"github.com/juju/names/v5"
"github.com/juju/juju/caas"
"github.com/juju/juju/controller"
"github.com/juju/juju/core/model"
"github.com/juju/juju/core/permission"
"github.com/juju/juju/environs"
environscloudspec "github.com/juju/juju/environs/cloudspec"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/feature"
"github.com/juju/juju/jujuclient"
)
const (
// ControllerModelName is the name of the admin model in each controller.
ControllerModelName = "controller"
// ControllerCharmName is the name of the controller charm.
ControllerCharmName = "juju-controller"
// ControllerApplicationName is the name of the controller application.
ControllerApplicationName = "controller"
// ControllerCharmArchive is the name of the controller charm archive.
ControllerCharmArchive = "controller.charm"
)
// PrepareParams contains the parameters for preparing a controller Environ
// for bootstrapping.
type PrepareParams struct {
// ModelConfig contains the base configuration for the controller model.
//
// This includes the model name, cloud type, any user-supplied
// configuration, config inherited from controller, and any defaults.
ModelConfig map[string]interface{}
// ControllerConfig is the configuration of the controller being prepared.
ControllerConfig controller.Config
// ControllerName is the name of the controller being prepared.
ControllerName string
// Cloud is the specification of the cloud that the controller is
// being prepared for.
Cloud environscloudspec.CloudSpec
// CredentialName is the name of the credential to use to bootstrap.
// This will be empty for auto-detected credentials.
CredentialName string
// AdminSecret contains the password for the admin user.
AdminSecret string
}
// Validate validates the PrepareParams.
func (p PrepareParams) Validate() error {
if err := p.ControllerConfig.Validate(); err != nil {
return errors.Annotate(err, "validating controller config")
}
if p.ControllerName == "" {
return errors.NotValidf("empty controller name")
}
if p.Cloud.Name == "" {
return errors.NotValidf("empty cloud name")
}
if p.AdminSecret == "" {
return errors.NotValidf("empty admin-secret")
}
return nil
}
// PrepareController prepares a new controller based on the provided configuration.
// It is an error to prepare a controller if there already exists an
// entry in the client store with the same name.
//
// Upon success, Prepare will update the ClientStore with the details of
// the controller, admin account, and admin model.
func PrepareController(
isCAASController bool,
ctx environs.BootstrapContext,
store jujuclient.ClientStore,
args PrepareParams,
) (environs.BootstrapEnviron, error) {
if err := args.Validate(); err != nil {
return nil, errors.Trace(err)
}
_, err := store.ControllerByName(args.ControllerName)
if err == nil {
return nil, errors.AlreadyExistsf("controller %q", args.ControllerName)
} else if !errors.IsNotFound(err) {
return nil, errors.Annotatef(err, "error reading controller %q info", args.ControllerName)
}
cloudType, ok := args.ModelConfig["type"].(string)
if !ok {
return nil, errors.NotFoundf("cloud type in base configuration")
}
p, err := environs.Provider(cloudType)
if err != nil {
return nil, errors.Trace(err)
}
cfg, details, err := prepare(ctx, p, args)
if err != nil {
return nil, errors.Trace(err)
}
do := func() error {
if err := decorateAndWriteInfo(
store, details, args.ControllerName, cfg.Name(),
); err != nil {
return errors.Annotatef(err, "cannot create controller %q info", args.ControllerName)
}
return nil
}
var env environs.BootstrapEnviron
openParams := environs.OpenParams{
ControllerUUID: args.ControllerConfig.ControllerUUID(),
Cloud: args.Cloud,
Config: cfg,
}
if isCAASController {
details.ModelType = model.CAAS
env, err = caas.Open(ctx.Context(), p, openParams)
} else {
details.ModelType = model.IAAS
env, err = environs.Open(ctx.Context(), p, openParams)
}
if err != nil {
return nil, errors.Trace(err)
}
if err := env.PrepareForBootstrap(ctx, args.ControllerName); err != nil {
return nil, errors.Trace(err)
}
if err := do(); err != nil {
return nil, errors.Trace(err)
}
return env, nil
}
// decorateAndWriteInfo decorates the info struct with information
// from the given cfg, and the writes that out to the filesystem.
func decorateAndWriteInfo(
store jujuclient.ClientStore,
details prepareDetails,
controllerName, modelName string,
) error {
qualifiedModelName := jujuclient.JoinOwnerModelName(
names.NewUserTag(details.AccountDetails.User),
modelName,
)
if err := store.AddController(controllerName, details.ControllerDetails); err != nil {
return errors.Trace(err)
}
if err := store.UpdateBootstrapConfig(controllerName, details.BootstrapConfig); err != nil {
return errors.Trace(err)
}
if err := store.UpdateAccount(controllerName, details.AccountDetails); err != nil {
return errors.Trace(err)
}
if err := store.UpdateModel(controllerName, qualifiedModelName, details.ModelDetails); err != nil {
return errors.Trace(err)
}
if err := store.SetCurrentModel(controllerName, qualifiedModelName); err != nil {
return errors.Trace(err)
}
return nil
}
func prepare(
ctx environs.BootstrapContext,
p environs.EnvironProvider,
args PrepareParams,
) (*config.Config, prepareDetails, error) {
var details prepareDetails
cfg, err := config.New(config.NoDefaults, args.ModelConfig)
if err != nil {
return cfg, details, errors.Trace(err)
}
cfg, err = p.PrepareConfig(environs.PrepareConfigParams{Cloud: args.Cloud, Config: cfg})
if err != nil {
return cfg, details, errors.Trace(err)
}
// We store the base configuration only; we don't want the
// default attributes, generated secrets/certificates, or
// UUIDs stored in the bootstrap config. Make a copy, so
// we don't disturb the caller's config map.
details.Config = make(map[string]interface{})
for k, v := range args.ModelConfig {
details.Config[k] = v
}
delete(details.Config, config.UUIDKey)
// TODO(axw) change signature of CACert() to not return a bool.
// It's no longer possible to have a controller config without
// a CA certificate.
caCert, ok := args.ControllerConfig.CACert()
if !ok {
return cfg, details, errors.New("controller config is missing CA certificate")
}
// We want to store attributes describing how a controller has been configured.
// These do not include the CACert or UUID since they will be replaced with new
// values when/if we need to use this configuration.
details.ControllerConfig = make(controller.Config)
for k, v := range args.ControllerConfig {
if k == controller.CACertKey || k == controller.ControllerUUIDKey {
continue
}
details.ControllerConfig[k] = v
}
for k, v := range args.ControllerConfig {
if k == controller.CACertKey || k == controller.ControllerUUIDKey {
continue
}
details.ControllerConfig[k] = v
}
details.CACert = caCert
details.ControllerUUID = args.ControllerConfig.ControllerUUID()
details.ControllerModelUUID = args.ModelConfig[config.UUIDKey].(string)
details.User = environs.AdminUser
details.Password = args.AdminSecret
details.LastKnownAccess = string(permission.SuperuserAccess)
details.ModelUUID = cfg.UUID()
if featureflag.Enabled(feature.Branches) || featureflag.Enabled(feature.Generations) {
details.ActiveBranch = model.GenerationMaster
}
details.ControllerDetails.Cloud = args.Cloud.Name
details.ControllerDetails.CloudRegion = args.Cloud.Region
details.ControllerDetails.CloudType = args.Cloud.Type
details.BootstrapConfig.CloudType = args.Cloud.Type
details.BootstrapConfig.Cloud = args.Cloud.Name
details.BootstrapConfig.CloudRegion = args.Cloud.Region
details.BootstrapConfig.CloudCACertificates = args.Cloud.CACertificates
details.BootstrapConfig.SkipTLSVerify = args.Cloud.SkipTLSVerify
details.CloudEndpoint = args.Cloud.Endpoint
details.CloudIdentityEndpoint = args.Cloud.IdentityEndpoint
details.CloudStorageEndpoint = args.Cloud.StorageEndpoint
details.Credential = args.CredentialName
if args.Cloud.SkipTLSVerify {
if len(args.Cloud.CACertificates) > 0 && args.Cloud.CACertificates[0] != "" {
return cfg, details, errors.NotValidf("cloud with both skip-TLS-verify=true and CA certificates")
}
logger.Warningf("controller %v is configured to skip validity checks on the server's certificate", args.ControllerName)
}
return cfg, details, nil
}
type prepareDetails struct {
jujuclient.ControllerDetails
jujuclient.BootstrapConfig
jujuclient.AccountDetails
jujuclient.ModelDetails
}