/
a1upgrade.go
452 lines (393 loc) · 12.7 KB
/
a1upgrade.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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
/*
Copyright © 2018 Chef Software
The a1upgrade package represents an A1 -> A2 upgrade. It is intended to extract
the A1 configuration and data and merge it into a deployable A2 deploy
configuration. It will then transform the any requisite service data, and deploy
the services with the transformed data and configuration.
*/
package a1upgrade
import (
"time"
dc "github.com/chef/automate/api/config/deployment"
)
const (
/*
NOTE: Right now this might be true, but in the future when we support
migrating to non-local targets I can see a case where the user both
provides an A2 config to us that describes the target and then we'll
also want to analyze the A1 config and merge it into the user provided
A2 config. I guess we'll cross that bridge when we get to it.
*/
skipA1ConfigImportMsg = // nolint: deadcode, varcheck, megacheck
`Chef Automate v2 config provided, skipping analysis of v1 configuration:
You have provided a Chef Automate v2 configuration file at the path below. No
settings will be imported from your Chef Automate v1 configuration. If this is
not intended, simply exit the upgrade and delete the file or omit the --config
option to the upgrade command as appropriate and rerun the upgrade command.
%s
`
)
// A1Upgrade represents a given A1Upgrade
type A1Upgrade struct {
CreatedAt time.Time
*A1Config
A2Config *dc.AutomateConfig
SkipUpgradePreflight bool
origin string
hartifacts string
channel string
upgradeStrategy string
adminPassword string
manifestDir string
PgDumpWait int
PgRestoreWait int
SkipBackup bool
FileMoveTimeout int
SkipBackupCheck bool
SkipDisasterRecoveryCheck bool
SkipExternalESCheck bool
SkipFIPSCheck bool
SkipSAMLCheck bool
SkipWorkflowCheck bool
EnableChefServer bool
EnableWorkflow bool
}
// Option represents an option that can be applied to an
// A1Upgrade. This is implemented using the "functional options"
// pattern. Various functions return an Option which can then be
// passed as arguments to NewA1Upgrade
//
// ```
// upgrade := NewA1Upgrade(a1upgrade.WithDeliverySecrets("/some/path.json"),
//
// a1upgrade.WithDeliveryRunning("/some/other/path.json"))
//
// ```
type Option func(*A1Upgrade) error
// NewA1Upgrade initializes a new A1Upgrade and applies any provided optional
// functions.
func NewA1Upgrade(options ...Option) (*A1Upgrade, error) {
u := A1Upgrade{
CreatedAt: time.Now(),
A1Config: NewA1Config(),
A2Config: dc.NewUserOverrideConfig(),
}
for _, option := range options {
if err := option(&u); err != nil {
return nil, err
}
}
if u.adminPassword == "" {
generatedPw, err := dc.GeneratePassword()
if err != nil {
return nil, err
}
u.adminPassword = generatedPw
}
err := u.A2Config.AddCredentials("Local Administrator", "admin", u.adminPassword)
return &u, err
}
// WithDeliverySecrets returns an Option that sets the path to the
// Automate v1 Secrets file that should be used during the upgrade.
func WithDeliverySecrets(path string) Option {
return func(u *A1Upgrade) error {
u.DeliverySecretsPath = path
return u.LoadDeliverySecrets()
}
}
// WithDeliveryRunning returns an Option that sets the path to the
// Automate v1 configuration that should be used during the upgrade.
func WithDeliveryRunning(path string) Option {
return func(u *A1Upgrade) error {
u.DeliveryRunningPath = path
return u.LoadDeliveryRunning()
}
}
// WithDeliveryRunning return an Option that sets the path to
// chef-server-running.json and loads it if EnableChefServer is true.
func WithChefServerRunning(path string, EnableChefServer bool) Option {
// The EnableChefServer argument is kinda redundant with the
// WithChefServerEnabled functional option, but we need it here so we do not
// depend on the Options being given/processed in a specific order; i.e., if
// we check for u.EnableChefServer then we would have to process the
// WithChefServerEnabled option before this one.
return func(u *A1Upgrade) error {
if !EnableChefServer {
return nil
}
u.ChefServerRunningPath = path
return u.LoadChefServerRunning()
}
}
// WithHartifactsPath returns an Option that sets the hartifacts path
// in the A2 config.
func WithHartifactsPath(path string) Option {
return func(u *A1Upgrade) error {
return u.SetHartifactsPath(path)
}
}
// WithOverrideOrigin returns an Option that sets the override the in
// the A2 config.
func WithOverrideOrigin(origin string) Option {
return func(u *A1Upgrade) error {
return u.SetOverrideOrigin(origin)
}
}
// WithManifestDir returns an Option that sets the manifest directory
// in the A2 config.
func WithManifestDir(path string) Option {
return func(u *A1Upgrade) error {
return u.SetManifestDir(path)
}
}
// WithChannel returns an Option that sets the channel the in the A2
// config.
func WithChannel(channel string) Option {
return func(u *A1Upgrade) error {
return u.SetChannel(channel)
}
}
// WithUpgradeStrategy returns an Option that sets the upgrade strategy the in
// the A2 config.
func WithUpgradeStrategy(strategy string) Option {
return func(u *A1Upgrade) error {
return u.SetUpgradeStrategy(strategy)
}
}
// WithAdminPassword returns an Option that sets the admin password in
// the A2 configuration.
func WithAdminPassword(password string) Option {
return func(u *A1Upgrade) error {
u.adminPassword = password
return u.SetAdminPassword(password)
}
}
// WithA2Config returns an Option option that sets the a2 config that
// should be used for the upgrade.
func WithA2Config(a2Config *dc.AutomateConfig) Option {
return func(u *A1Upgrade) error {
return u.SetA2Config(a2Config)
}
}
// WithA2ConfigPath returns an Option that sets the path to a TOML
// representation of the configuration that should be used for the
// upgrade.
func WithA2ConfigPath(path string, options ...dc.AutomateConfigOpt) Option {
return func(u *A1Upgrade) error {
if path == "" {
return nil
}
a2Config, err := dc.LoadUserOverrideConfigFile(path, options...)
if err != nil {
return err
}
return u.SetA2Config(a2Config)
}
}
func WithWorkflowEnabled(enabled bool) Option {
return func(u *A1Upgrade) error {
u.EnableWorkflow = enabled
return nil
}
}
func WithChefServerEnabled(enabled bool) Option {
return func(u *A1Upgrade) error {
u.EnableChefServer = enabled
return nil
}
}
// SkipUpgradePreflight returns an Option that indicates whether
// to skip the upgrade preflight checks.
func SkipUpgradePreflight(skip bool) Option {
return func(u *A1Upgrade) error {
u.SkipUpgradePreflight = skip
return nil
}
}
// SetPostgresDumpWait returns an Option that sets the PostgreSQL dump
// timeout in seconds.
func SetPostgresDumpWait(seconds int) Option {
return func(u *A1Upgrade) error {
u.PgDumpWait = seconds
return nil
}
}
// SetPostgresRestoreWait returns an Option that sets the PostgreSQL
// restore timeout in seconds.
func SetPostgresRestoreWait(seconds int) Option {
return func(u *A1Upgrade) error {
u.PgRestoreWait = seconds
return nil
}
}
// SkipUpgradeBackup returns an Option that indicates whether
// to skip A1 backup during upgrade.
func SkipUpgradeBackup(backup bool) Option {
return func(u *A1Upgrade) error {
u.SkipBackup = backup
return nil
}
}
// SetFileMoveTimeout returns an Option that sets the timeout in
// seconds for moving files during upgrade from A1.
func SetFileMoveTimeout(seconds int) Option {
return func(u *A1Upgrade) error {
u.FileMoveTimeout = seconds
return nil
}
}
// SetA2Config replaces upgrade's A2Config with the given a2Config
func (u *A1Upgrade) SetA2Config(a2Config *dc.AutomateConfig) error {
u.A2Config = a2Config
return nil
}
// SetHartifactsPath overrides the hartifacts path.
// configuration during initialization.
func (u *A1Upgrade) SetHartifactsPath(path string) error {
u.hartifacts = path
return u.A2Config.SetHartifactsPath(path)
}
// SetOverrideOrigin sets the override origin for the upgrade.
func (u *A1Upgrade) SetOverrideOrigin(origin string) error {
u.origin = origin
return u.A2Config.SetOverrideOrigin(origin)
}
// SetManifestDir sets the manifest dir for the upgrade.
func (u *A1Upgrade) SetManifestDir(dir string) error {
u.manifestDir = dir
return u.A2Config.SetManifestDir(dir)
}
// SetChannel sets the channel for the upgrade.
func (u *A1Upgrade) SetChannel(channel string) error {
u.channel = channel
return u.A2Config.SetChannel(channel)
}
// SetUpgradeStrategy sets the upgrade strategy for the upgrade.
func (u *A1Upgrade) SetUpgradeStrategy(strategy string) error {
u.upgradeStrategy = strategy
return u.A2Config.SetUpgradeStrategy(strategy)
}
// SetAdminPassword sets the admin password for the upgrade.
func (u *A1Upgrade) SetAdminPassword(password string) error {
return u.A2Config.SetAdminPassword(password)
}
func (u *A1Upgrade) SetChefServerEnabled(enabled bool) error {
return u.A2Config.SetChefServerEnabled(enabled)
}
func (u *A1Upgrade) SetWorkflowEnabled(enabled bool) error {
return u.A2Config.SetWorkflowEnabled(enabled)
}
// Databases returns a slice of A1Databases that need to me migrated
func (u *A1Upgrade) Databases() []A1Database {
// Default database that we will migrate
// NOTE: This only includes tables related to compliance & secrets services
deliveryDB := A1DeliveryDB
// If Workflow is enabled, we backup everything from the delivery database
// the main reason is that we need types and functions attached to it
if u.EnableWorkflow {
deliveryDB.includedTables = []string{}
deliveryDB.excludedTables = []string{"pg_stat_repl"}
}
dbs := []A1Database{deliveryDB}
// If ChefServer is enabled, we append more databases
if u.EnableChefServer {
dbs = append(dbs, CSDatabases...)
}
return dbs
}
// SkipBackupConfiguredCheck returns an Option that indicates whether
// to skip the A1 upgrade preflight check for configured backups.
func SkipBackupConfiguredCheck(backup bool) Option {
return func(u *A1Upgrade) error {
u.SkipBackupCheck = backup
return nil
}
}
// SkipDisasterRecoveryConfiguredCheck returns an Option that indicates whether
// to skip the A1 upgrade preflight check for disaster recovery.
func SkipDisasterRecoveryConfiguredCheck(dr bool) Option {
return func(u *A1Upgrade) error {
u.SkipDisasterRecoveryCheck = dr
return nil
}
}
// SkipExternalESConfiguredCheck returns an Option that indicates whether
// to skip A1 upgrade preflight check for external elasticsearch.
func SkipExternalESConfiguredCheck(es bool) Option {
return func(u *A1Upgrade) error {
u.SkipExternalESCheck = es
return nil
}
}
// SkipFIPSConfiguredCheck returns an Option that indicates whether
// to skip A1 upgrade preflight check for FIPS.
func SkipFIPSConfiguredCheck(fips bool) Option {
return func(u *A1Upgrade) error {
u.SkipFIPSCheck = fips
return nil
}
}
// SkipSAMLConfiguredCheck returns an Option that indicates whether
// to skip A1 upgrade preflight check for SAML.
func SkipSAMLConfiguredCheck(saml bool) Option {
return func(u *A1Upgrade) error {
u.SkipSAMLCheck = saml
return nil
}
}
// SkipWorkflowConfiguredCheck returns an Option that indicates whether
// to skip the A1 upgrade preflight check for workflow.
func SkipWorkflowConfiguredCheck(workflow bool) Option {
return func(u *A1Upgrade) error {
u.SkipWorkflowCheck = workflow
return nil
}
}
// GenerateA2ConfigIfNoneProvided creates an Automate 2 configuration from
// Automate 1 configuration if a2ConfigPath is the zero value for a string
// (""). If a2ConfigPath is not the zero value, it's assumed that
// 1. an a2 config has been loaded via the initializer options, and:
// 2. the a2 config that we loaded contains user customizations that we must
// not override.
func (u *A1Upgrade) GenerateA2ConfigIfNoneProvided(a2ConfigPath string) error {
if a2ConfigPath != "" {
return nil
}
cfg, err := generateMigrationOverrideConfig(u.DeliveryRunning, u.DeliverySecrets, u.ChefServerRunning)
if err != nil {
return err
}
u.A2Config = cfg
// Ensure we re-apply any a2 config that was previously set.
// There may be a prettier way to do this but this way works regardless of
// what order we get the options
err = u.SetHartifactsPath(u.hartifacts)
if err != nil {
return err
}
err = u.SetOverrideOrigin(u.origin)
if err != nil {
return err
}
err = u.SetChannel(u.channel)
if err != nil {
return err
}
err = u.SetAdminPassword(u.adminPassword)
if err != nil {
return err
}
err = u.SetManifestDir(u.manifestDir)
if err != nil {
return err
}
err = u.SetChefServerEnabled(u.EnableChefServer)
if err != nil {
return err
}
err = u.SetWorkflowEnabled(u.EnableWorkflow)
if err != nil {
return err
}
return u.A2Config.ValidateWithGlobalAndDefaults()
}