cloud-mu 1.9.0.pre.beta → 2.0.0.pre.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Berksfile +16 -54
- data/Berksfile.lock +14 -62
- data/bin/mu-aws-setup +131 -108
- data/bin/mu-configure +311 -74
- data/bin/mu-gcp-setup +84 -62
- data/bin/mu-load-config.rb +46 -2
- data/bin/mu-self-update +11 -9
- data/bin/mu-upload-chef-artifacts +4 -4
- data/{mu.gemspec → cloud-mu.gemspec} +2 -2
- data/cookbooks/awscli/Berksfile +8 -0
- data/cookbooks/mu-activedirectory/Berksfile +11 -0
- data/cookbooks/mu-firewall/Berksfile +9 -0
- data/cookbooks/mu-firewall/metadata.rb +1 -1
- data/cookbooks/mu-glusterfs/Berksfile +10 -0
- data/cookbooks/mu-jenkins/Berksfile +14 -0
- data/cookbooks/mu-master/Berksfile +23 -0
- data/cookbooks/mu-master/attributes/default.rb +1 -1
- data/cookbooks/mu-master/metadata.rb +2 -2
- data/cookbooks/mu-master/recipes/default.rb +1 -1
- data/cookbooks/mu-master/recipes/init.rb +7 -3
- data/cookbooks/mu-master/recipes/ssl-certs.rb +1 -0
- data/cookbooks/mu-mongo/Berksfile +10 -0
- data/cookbooks/mu-openvpn/Berksfile +11 -0
- data/cookbooks/mu-php54/Berksfile +13 -0
- data/cookbooks/mu-splunk/Berksfile +10 -0
- data/cookbooks/mu-tools/Berksfile +21 -0
- data/cookbooks/mu-tools/files/default/Mu_CA.pem +15 -15
- data/cookbooks/mu-utility/Berksfile +9 -0
- data/cookbooks/mu-utility/metadata.rb +2 -1
- data/cookbooks/nagios/Berksfile +7 -4
- data/cookbooks/s3fs/Berksfile +9 -0
- data/environments/dev.json +6 -6
- data/environments/prod.json +6 -6
- data/modules/mu.rb +20 -42
- data/modules/mu/cleanup.rb +102 -100
- data/modules/mu/cloud.rb +90 -28
- data/modules/mu/clouds/aws.rb +449 -218
- data/modules/mu/clouds/aws/alarm.rb +29 -17
- data/modules/mu/clouds/aws/cache_cluster.rb +78 -64
- data/modules/mu/clouds/aws/collection.rb +25 -18
- data/modules/mu/clouds/aws/container_cluster.rb +73 -66
- data/modules/mu/clouds/aws/database.rb +124 -116
- data/modules/mu/clouds/aws/dnszone.rb +27 -20
- data/modules/mu/clouds/aws/firewall_rule.rb +30 -22
- data/modules/mu/clouds/aws/folder.rb +18 -3
- data/modules/mu/clouds/aws/function.rb +77 -23
- data/modules/mu/clouds/aws/group.rb +19 -12
- data/modules/mu/clouds/aws/habitat.rb +153 -0
- data/modules/mu/clouds/aws/loadbalancer.rb +59 -52
- data/modules/mu/clouds/aws/log.rb +30 -23
- data/modules/mu/clouds/aws/msg_queue.rb +29 -20
- data/modules/mu/clouds/aws/notifier.rb +222 -0
- data/modules/mu/clouds/aws/role.rb +178 -90
- data/modules/mu/clouds/aws/search_domain.rb +40 -24
- data/modules/mu/clouds/aws/server.rb +169 -137
- data/modules/mu/clouds/aws/server_pool.rb +60 -83
- data/modules/mu/clouds/aws/storage_pool.rb +59 -31
- data/modules/mu/clouds/aws/user.rb +36 -27
- data/modules/mu/clouds/aws/userdata/linux.erb +101 -93
- data/modules/mu/clouds/aws/vpc.rb +250 -189
- data/modules/mu/clouds/azure.rb +132 -0
- data/modules/mu/clouds/cloudformation.rb +65 -1
- data/modules/mu/clouds/cloudformation/alarm.rb +8 -0
- data/modules/mu/clouds/cloudformation/cache_cluster.rb +7 -0
- data/modules/mu/clouds/cloudformation/collection.rb +7 -0
- data/modules/mu/clouds/cloudformation/database.rb +7 -0
- data/modules/mu/clouds/cloudformation/dnszone.rb +7 -0
- data/modules/mu/clouds/cloudformation/firewall_rule.rb +9 -2
- data/modules/mu/clouds/cloudformation/loadbalancer.rb +7 -0
- data/modules/mu/clouds/cloudformation/log.rb +7 -0
- data/modules/mu/clouds/cloudformation/server.rb +7 -0
- data/modules/mu/clouds/cloudformation/server_pool.rb +7 -0
- data/modules/mu/clouds/cloudformation/vpc.rb +7 -0
- data/modules/mu/clouds/google.rb +214 -110
- data/modules/mu/clouds/google/container_cluster.rb +42 -24
- data/modules/mu/clouds/google/database.rb +15 -6
- data/modules/mu/clouds/google/firewall_rule.rb +17 -25
- data/modules/mu/clouds/google/group.rb +13 -5
- data/modules/mu/clouds/google/habitat.rb +105 -0
- data/modules/mu/clouds/google/loadbalancer.rb +28 -20
- data/modules/mu/clouds/google/server.rb +93 -354
- data/modules/mu/clouds/google/server_pool.rb +18 -10
- data/modules/mu/clouds/google/user.rb +22 -14
- data/modules/mu/clouds/google/vpc.rb +97 -69
- data/modules/mu/config.rb +133 -38
- data/modules/mu/config/alarm.rb +25 -0
- data/modules/mu/config/cache_cluster.rb +5 -3
- data/modules/mu/config/cache_cluster.yml +23 -0
- data/modules/mu/config/database.rb +25 -16
- data/modules/mu/config/database.yml +3 -3
- data/modules/mu/config/function.rb +1 -2
- data/modules/mu/config/{project.rb → habitat.rb} +10 -10
- data/modules/mu/config/notifier.rb +85 -0
- data/modules/mu/config/notifier.yml +9 -0
- data/modules/mu/config/role.rb +1 -1
- data/modules/mu/config/search_domain.yml +2 -2
- data/modules/mu/config/server.rb +13 -1
- data/modules/mu/config/server.yml +3 -3
- data/modules/mu/config/server_pool.rb +3 -1
- data/modules/mu/config/storage_pool.rb +3 -1
- data/modules/mu/config/storage_pool.yml +19 -0
- data/modules/mu/config/vpc.rb +70 -8
- data/modules/mu/groomers/chef.rb +2 -3
- data/modules/mu/kittens.rb +500 -122
- data/modules/mu/master.rb +5 -5
- data/modules/mu/mommacat.rb +151 -91
- data/modules/tests/super_complex_bok.yml +12 -0
- data/modules/tests/super_simple_bok.yml +12 -0
- data/spec/mu/clouds/azure_spec.rb +82 -0
- data/spec/spec_helper.rb +105 -0
- metadata +26 -5
- data/modules/mu/clouds/aws/notification.rb +0 -139
- data/modules/mu/config/notification.rb +0 -44
data/bin/mu-configure
CHANGED
@@ -127,15 +127,24 @@ $CONFIGURABLES = {
|
|
127
127
|
},
|
128
128
|
"aws" => {
|
129
129
|
"title" => "Amazon Web Services",
|
130
|
+
"named_subentries" => true,
|
130
131
|
"subtree" => {
|
131
132
|
"account_number" => {
|
132
|
-
"title" => "Account
|
133
|
-
"desc" => "
|
133
|
+
"title" => "Default Target Account",
|
134
|
+
"desc" => "Default target account for resources managed using these credentials. This is an AWS account number, e.g. 918972669773. If not specified, we will use the account number which owns these API keys.",
|
134
135
|
"pattern" => /^\d+$/
|
135
136
|
},
|
136
137
|
"region" => {
|
137
138
|
"title" => "Default Region",
|
138
|
-
"desc" => "Default Amazon Web Services region in which
|
139
|
+
"desc" => "Default Amazon Web Services region in which these credentials should operate"
|
140
|
+
},
|
141
|
+
"credentials" => {
|
142
|
+
"title" => "Credentials Vault:Item",
|
143
|
+
"desc" => "A secure Chef vault and item from which to retrieve an AWS access key and secret. The vault item should have 'access_key' and 'access_secret' elements."
|
144
|
+
},
|
145
|
+
"credentials_file" => {
|
146
|
+
"title" => "Credentials File",
|
147
|
+
"desc" => "An INI-formatted AWS credentials file, of the type used by the AWS command-line tools. This is less secure than using 'credentials' to store these in a Chef vault. See: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html"
|
139
148
|
},
|
140
149
|
"access_key" => {
|
141
150
|
"title" => "Access Key",
|
@@ -144,25 +153,38 @@ $CONFIGURABLES = {
|
|
144
153
|
},
|
145
154
|
"access_secret" => {
|
146
155
|
"title" => "Access Secret",
|
147
|
-
"desc" => "Credentials used for accessing the AWS API (looks like: +Z16iRP9QAq7EcjHINyEMs3oR7A76QpfaSgCBogp)"
|
156
|
+
"desc" => "Credentials used for accessing the AWS API (looks like: +Z16iRP9QAq7EcjHINyEMs3oR7A76QpfaSgCBogp)."
|
148
157
|
},
|
149
158
|
"log_bucket_name" => {
|
150
159
|
"title" => "Log and Secret Bucket Name",
|
151
160
|
"desc" => "S3 bucket into which we'll synchronize deploy secrets, and if we're hosted in AWS, collected system logs",
|
161
|
+
"required" => true,
|
152
162
|
"changes" => ["chefrun"]
|
163
|
+
},
|
164
|
+
"default" => {
|
165
|
+
"title" => "Is Default",
|
166
|
+
"default" => false,
|
167
|
+
"desc" => "If set to true, Mu will default to these AWS credentials when targeting AWS resources",
|
168
|
+
"boolean" => true
|
153
169
|
}
|
154
170
|
}
|
155
171
|
},
|
156
172
|
"google" => {
|
157
173
|
"title" => "Google Cloud Platform",
|
174
|
+
"named_subentries" => true,
|
158
175
|
"subtree" => {
|
159
176
|
"project" => {
|
160
177
|
"title" => "Default Project",
|
178
|
+
"required" => true,
|
161
179
|
"desc" => "Default Google Cloud Platform project in which we operate and deploy. Generate a service account at: https://console.cloud.google.com/iam-admin/serviceaccounts/project, making sure the account has sufficient privileges to manage cloud resources. Download the private key as JSON, and import that key to the vault specified here. Import example: knife vault create secrets google -J my-google-service-account.json"
|
162
180
|
},
|
163
181
|
"credentials" => {
|
164
182
|
"title" => "Credentials Vault:Item",
|
165
|
-
"desc" => "A vault and item from which to retrieve the JSON-formatted Service Account credentials for our GCP account, in the format vault:itemname (e.g. 'secrets:google'). Generate a service account at: https://console.cloud.google.com/iam-admin/serviceaccounts/project, making sure the account has sufficient privileges to manage cloud resources. Download the private key as JSON, and import that key to the vault specified here. Import example: knife vault create secrets google -J my-google-service-account.json "
|
183
|
+
"desc" => "A secure Chef vault and item from which to retrieve the JSON-formatted Service Account credentials for our GCP account, in the format vault:itemname (e.g. 'secrets:google'). Generate a service account at: https://console.cloud.google.com/iam-admin/serviceaccounts/project, making sure the account has sufficient privileges to manage cloud resources. Download the private key as JSON, and import that key to the vault specified here. Import example: knife vault create secrets google -J my-google-service-account.json "
|
184
|
+
},
|
185
|
+
"credentials_file" => {
|
186
|
+
"title" => "Credentials File",
|
187
|
+
"desc" => "JSON-formatted Service Account credentials for our GCP account, stored in plain text in a file. Generate a service account at: https://console.cloud.google.com/iam-admin/serviceaccounts/project, making sure the account has sufficient privileges to manage cloud resources. Download the private key as JSON and point this argument to the file. This is less secure than using 'credentials' to store in a vault."
|
166
188
|
},
|
167
189
|
"region" => {
|
168
190
|
"title" => "Default Region",
|
@@ -172,7 +194,48 @@ $CONFIGURABLES = {
|
|
172
194
|
"log_bucket_name" => {
|
173
195
|
"title" => "Log and Secret Bucket Name",
|
174
196
|
"desc" => "Cloud Storage bucket into which we'll synchronize deploy secrets, and if we're hosted in GCP, collected system logs",
|
197
|
+
"required" => true,
|
175
198
|
"changes" => ["chefrun"]
|
199
|
+
},
|
200
|
+
"default" => {
|
201
|
+
"title" => "Is Default Account",
|
202
|
+
"default" => false,
|
203
|
+
"desc" => "If set to true, Mu will use this set of GCP credentials when targeting the Google Cloud without a specific account having been requested",
|
204
|
+
"boolean" => true
|
205
|
+
}
|
206
|
+
}
|
207
|
+
},
|
208
|
+
"azure" => {
|
209
|
+
"title" => "Microsoft Azure Cloud Computing Platform & Services",
|
210
|
+
"named_subentries" => true,
|
211
|
+
"subtree" => {
|
212
|
+
"subscription" => {
|
213
|
+
"title" => "Default Subscription",
|
214
|
+
"desc" => "Default Microsoft Azure Directory project in which we operate and deploy."
|
215
|
+
},
|
216
|
+
"credentials" => {
|
217
|
+
"title" => "Credentials Vault:Item",
|
218
|
+
"desc" => "A secure Chef vault and item from which to retrieve the JSON-formatted Service Account credentials for our Azure account, in the format vault:itemname (e.g. 'secrets:google'). Generate a service account at: https://console.cloud.google.com/iam-admin/serviceaccounts/project, making sure the account has sufficient privileges to manage cloud resources. Download the private key as JSON, and import that key to the vault specified here. Import example: knife vault create secrets google -J my-google-service-account.json "
|
219
|
+
},
|
220
|
+
"credentials_file" => {
|
221
|
+
"title" => "Credentials File",
|
222
|
+
"desc" => "JSON-formatted Service Account credentials for our Azure account, stored in plain text in a file."
|
223
|
+
},
|
224
|
+
"region" => {
|
225
|
+
"title" => "Default Region",
|
226
|
+
"desc" => "Default Microsoft Azure region in which we operate and deploy",
|
227
|
+
"default" => "eastus"
|
228
|
+
},
|
229
|
+
"log_bucket_name" => {
|
230
|
+
"title" => "Log and Secret Bucket Name",
|
231
|
+
"desc" => "Cloud Storage bucket into which we'll synchronize deploy secrets, and if we're hosted in Azure, collected system logs",
|
232
|
+
"changes" => ["chefrun"]
|
233
|
+
},
|
234
|
+
"default" => {
|
235
|
+
"title" => "Is Default Account",
|
236
|
+
"default" => false,
|
237
|
+
"desc" => "If set to true, Mu will use this set of Azure credentials when targeting Azure without a specific account having been requested",
|
238
|
+
"boolean" => true
|
176
239
|
}
|
177
240
|
}
|
178
241
|
}
|
@@ -246,6 +309,14 @@ begin
|
|
246
309
|
end
|
247
310
|
rescue OpenURI::HTTPError, Timeout::Error, SocketError, Errno::ENETUNREACH
|
248
311
|
end
|
312
|
+
$IN_AZURE = false
|
313
|
+
begin
|
314
|
+
Timeout.timeout(2) do
|
315
|
+
instance_id = open("http://169.254.169.254/metadata/instance/compute").read
|
316
|
+
$IN_AWS = true if !instance_id.nil? and instance_id.size > 0
|
317
|
+
end
|
318
|
+
rescue OpenURI::HTTPError, Timeout::Error, SocketError, Errno::ENETUNREACH
|
319
|
+
end
|
249
320
|
|
250
321
|
|
251
322
|
KNIFE_TEMPLATE = "log_level :info
|
@@ -283,27 +354,70 @@ ssl_verify_mode :verify_none
|
|
283
354
|
|
284
355
|
$CHANGES = []
|
285
356
|
|
357
|
+
def cloneHash(hash)
|
358
|
+
new = {}
|
359
|
+
hash.each_pair { |k,v|
|
360
|
+
if v.is_a?(Hash)
|
361
|
+
new[k] = cloneHash(v)
|
362
|
+
elsif !v.nil?
|
363
|
+
new[k] = v.dup
|
364
|
+
end
|
365
|
+
}
|
366
|
+
new
|
367
|
+
end
|
286
368
|
|
287
369
|
$MENU_MAP = {}
|
288
|
-
def assignMenuEntries
|
370
|
+
def assignMenuEntries(tree = $CONFIGURABLES, map = $MENU_MAP)
|
289
371
|
count = 1
|
290
|
-
|
372
|
+
tree.each_pair { |key, data|
|
373
|
+
next if !data.is_a?(Hash)
|
291
374
|
next if !AMROOT and data['rootonly']
|
292
375
|
if data.has_key?("subtree")
|
293
376
|
letters = ("a".."z").to_a
|
294
377
|
lettercount = 0
|
295
|
-
data[
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
378
|
+
if data['named_subentries']
|
379
|
+
# Generate a stub entry for adding a new item
|
380
|
+
map[count.to_s] = cloneHash(data["subtree"])
|
381
|
+
map[count.to_s].each_pair { |k, v| v.delete("value") } # use defaults
|
382
|
+
map[count.to_s]["name"] = {
|
383
|
+
"title" => "Name",
|
384
|
+
"desc" => "A name/alias for this account.",
|
385
|
+
"required" => true
|
386
|
+
}
|
387
|
+
map[count.to_s]["#addnew"] = true
|
388
|
+
map[count.to_s]["#key"] = key
|
389
|
+
|
390
|
+
# Now the menu entries for the existing ones
|
391
|
+
if data['subtree']['#entries']
|
392
|
+
data['subtree']['#entries'].each_pair { |nameentry, subdata|
|
393
|
+
next if data['readonly']
|
394
|
+
next if !subdata.is_a?(Hash)
|
395
|
+
subdata["#menu"] = count.to_s+letters[lettercount]
|
396
|
+
subdata["#title"] = nameentry
|
397
|
+
subdata["#key"] = key
|
398
|
+
subdata["#entries"] = cloneHash(data["subtree"]["#entries"])
|
399
|
+
subdata["is_submenu"] = true
|
400
|
+
map[count.to_s+letters[lettercount]] = tree[key]["subtree"]['#entries'][nameentry]
|
401
|
+
map[count.to_s+letters[lettercount]]['#entries'] ||= cloneHash(data["subtree"]["#entries"])
|
402
|
+
lettercount = lettercount + 1
|
403
|
+
}
|
404
|
+
end
|
405
|
+
else
|
406
|
+
data["subtree"].each_pair { |subkey, subdata|
|
407
|
+
next if !AMROOT and subdata['rootonly']
|
408
|
+
tree[key]["subtree"][subkey]["#menu"] = count.to_s+letters[lettercount]
|
409
|
+
tree[key]["subtree"][subkey]["#key"] = subkey
|
410
|
+
map[count.to_s+letters[lettercount]] = tree[key]["subtree"][subkey]
|
411
|
+
lettercount = lettercount + 1
|
412
|
+
}
|
413
|
+
end
|
301
414
|
end
|
302
|
-
|
303
|
-
|
415
|
+
tree[key]["#menu"] = count.to_s
|
416
|
+
tree[key]["#key"] = key
|
417
|
+
map[count.to_s] ||= tree[key]
|
304
418
|
count = count + 1
|
305
419
|
}
|
306
|
-
|
420
|
+
map#.freeze
|
307
421
|
end
|
308
422
|
|
309
423
|
def trySSHKeyWithGit(repo, keypath = nil)
|
@@ -467,13 +581,14 @@ def setDefaults
|
|
467
581
|
$CONFIGURABLES["jenkins"]["subtree"]["admin_email"]["default"] = $CONFIGURABLES["mu_admin_email"]["value"]
|
468
582
|
end
|
469
583
|
if $IN_AWS
|
584
|
+
# XXX move this crap to a callback hook for puttering around in the AWS submenu
|
470
585
|
aws = JSON.parse(open("http://169.254.169.254/latest/dynamic/instance-identity/document").read)
|
471
586
|
iam = nil
|
472
587
|
begin
|
473
588
|
iam = open("http://169.254.169.254/latest/meta-data/iam/security-credentials").read
|
474
589
|
rescue OpenURI::HTTPError, SocketError
|
475
590
|
end
|
476
|
-
$CONFIGURABLES["aws"]["subtree"]["account_number"]["default"] = aws["accountId"]
|
591
|
+
# $CONFIGURABLES["aws"]["subtree"]["account_number"]["default"] = aws["accountId"]
|
477
592
|
$CONFIGURABLES["aws"]["subtree"]["region"]["default"] = aws["region"]
|
478
593
|
if iam and iam.size > 0
|
479
594
|
# XXX can we think of a good way to test our permission set?
|
@@ -506,14 +621,16 @@ def importCLIValues
|
|
506
621
|
$CONFIGURABLES.each_pair { |key, data|
|
507
622
|
next if !AMROOT and data['rootonly']
|
508
623
|
if data.has_key?("subtree")
|
509
|
-
data[
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
624
|
+
if !data['named_subentries']
|
625
|
+
data["subtree"].each_pair { |subkey, subdata|
|
626
|
+
next if !AMROOT and subdata['rootonly']
|
627
|
+
if $opts[(subdata['cli-opt'].gsub(/-/, "_")+"_given").to_sym]
|
628
|
+
newval = runValueCallback(subdata, $opts[subdata['cli-opt'].gsub(/-/, "_").to_sym])
|
629
|
+
subdata["value"] = newval if !newval.nil?
|
630
|
+
$CHANGES.concat(subdata['changes']) if subdata['changes']
|
631
|
+
end
|
632
|
+
}
|
633
|
+
end
|
517
634
|
else
|
518
635
|
if $opts[(data['cli-opt'].gsub(/-/, "_")+"_given").to_sym]
|
519
636
|
newval = runValueCallback(data, $opts[data['cli-opt'].gsub(/-/, "_").to_sym])
|
@@ -532,10 +649,29 @@ def importCurrentValues
|
|
532
649
|
if $CONFIGURABLES[key].has_key?("subtree")
|
533
650
|
# It's a sub-tree. I'm too lazy to write a recursive thing for this, just
|
534
651
|
# cover the simple case that we actually care about for now.
|
535
|
-
$CONFIGURABLES[key]["
|
536
|
-
|
537
|
-
$
|
538
|
-
|
652
|
+
if $CONFIGURABLES[key]["named_subentries"]
|
653
|
+
$CONFIGURABLES[key]['subtree']["#title"] = $CONFIGURABLES[key]['title']
|
654
|
+
$MU_CFG[key].each_pair { |nameentry, subtree|
|
655
|
+
$CONFIGURABLES[key]['subtree']["#entries"] ||= {}
|
656
|
+
$CONFIGURABLES[key]['subtree']["#entries"][nameentry] = cloneHash($CONFIGURABLES[key]['subtree'])
|
657
|
+
$CONFIGURABLES[key]['subtree']["#entries"][nameentry].delete("#entries")
|
658
|
+
$CONFIGURABLES[key]["subtree"]["#entries"][nameentry]["name"] = {
|
659
|
+
"title" => "Name",
|
660
|
+
"desc" => "A name/alias for this account.",
|
661
|
+
"required" => true,
|
662
|
+
"value" => nameentry
|
663
|
+
}
|
664
|
+
$CONFIGURABLES[key]["subtree"].keys.each { |subkey|
|
665
|
+
next if !subtree.has_key?(subkey)
|
666
|
+
$CONFIGURABLES[key]["subtree"]["#entries"][nameentry][subkey]["value"] = subtree[subkey]
|
667
|
+
}
|
668
|
+
}
|
669
|
+
else
|
670
|
+
$CONFIGURABLES[key]["subtree"].keys.each { |subkey|
|
671
|
+
next if !$MU_CFG[key].has_key?(subkey)
|
672
|
+
$CONFIGURABLES[key]["subtree"][subkey]["value"] = $MU_CFG[key][subkey]
|
673
|
+
}
|
674
|
+
end
|
539
675
|
else
|
540
676
|
$CONFIGURABLES[key]["value"] = $MU_CFG[key]
|
541
677
|
end
|
@@ -545,7 +681,15 @@ end
|
|
545
681
|
def printVal(data)
|
546
682
|
valid = true
|
547
683
|
valid = validate(data["value"], data, false) if data["value"]
|
548
|
-
|
684
|
+
|
685
|
+
value = if data["value"] and data["value"] != ""
|
686
|
+
data["value"]
|
687
|
+
elsif data["default"] and data["default"] != ""
|
688
|
+
data["default"]
|
689
|
+
end
|
690
|
+
if data['readonly'] and value
|
691
|
+
print " - "+value.to_s.cyan.on_black
|
692
|
+
elsif !valid
|
549
693
|
print " "+data["value"].to_s.red.on_black
|
550
694
|
print " (consider default of #{data["default"].to_s.bold})" if data["default"]
|
551
695
|
elsif !data["value"].nil?
|
@@ -559,21 +703,35 @@ end
|
|
559
703
|
|
560
704
|
# Converts the current $CONFIGURABLES object to a Hash suitable for merging
|
561
705
|
# with $MU_CFG.
|
562
|
-
def setConfigTree
|
706
|
+
def setConfigTree(tree = $CONFIGURABLES)
|
563
707
|
cfg = {}
|
564
|
-
|
708
|
+
tree.each_pair { |key, data|
|
565
709
|
next if !AMROOT and data['rootonly']
|
566
710
|
if data.has_key?("subtree")
|
567
|
-
data["
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
711
|
+
if data["named_subentries"]
|
712
|
+
if data["subtree"]["#entries"]
|
713
|
+
data["subtree"]["#entries"].each_pair { |name, block|
|
714
|
+
|
715
|
+
next if !block.is_a?(Hash)
|
716
|
+
block.each_pair { |subkey, subdata|
|
717
|
+
next if subkey.match(/^#/) or !subdata.is_a?(Hash)
|
718
|
+
cfg[key] ||= {}
|
719
|
+
cfg[key][name] ||= {}
|
720
|
+
cfg[key][name][subkey] = subdata['value'] if subdata['value']
|
721
|
+
}
|
722
|
+
}
|
575
723
|
end
|
576
|
-
|
724
|
+
else
|
725
|
+
data["subtree"].each_pair { |subkey, subdata|
|
726
|
+
if !subdata["value"].nil?
|
727
|
+
cfg[key] ||= {}
|
728
|
+
cfg[key][subkey] = subdata["value"]
|
729
|
+
elsif !subdata["default"].nil? and !$HAVE_GLOBAL_CONFIG or ($MU_CFG and (!$MU_CFG[key] or !$MU_CFG[key][subkey]))
|
730
|
+
cfg[key] ||= {}
|
731
|
+
cfg[key][subkey] = subdata["default"]
|
732
|
+
end
|
733
|
+
}
|
734
|
+
end
|
577
735
|
elsif !data["value"].nil?
|
578
736
|
cfg[key] = data["value"]
|
579
737
|
elsif !data["default"].nil? and !$HAVE_GLOBAL_CONFIG or ($MU_CFG and !$MU_CFG[key])
|
@@ -583,20 +741,33 @@ def setConfigTree
|
|
583
741
|
cfg
|
584
742
|
end
|
585
743
|
|
586
|
-
def displayCurrentOpts
|
744
|
+
def displayCurrentOpts(tree = $CONFIGURABLES)
|
587
745
|
count = 1
|
588
746
|
optlist = []
|
589
|
-
|
747
|
+
tree.each_pair { |key, data|
|
590
748
|
next if !AMROOT and data['rootonly']
|
591
|
-
|
749
|
+
next if !data.is_a?(Hash)
|
750
|
+
if data["title"].nil? or data["#menu"].nil?
|
751
|
+
next
|
752
|
+
end
|
753
|
+
print data["#menu"].bold+") "+data["title"]
|
592
754
|
if data.has_key?("subtree")
|
593
755
|
puts ""
|
594
|
-
data["
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
756
|
+
if data["named_subentries"]
|
757
|
+
if data['subtree']['#entries']
|
758
|
+
data['subtree']['#entries'].each_pair { |nameentry, subdata|
|
759
|
+
next if nameentry.match(/^#/)
|
760
|
+
puts " "+subdata["#menu"].bold+". "+nameentry.green.on_black
|
761
|
+
}
|
762
|
+
end
|
763
|
+
else
|
764
|
+
data["subtree"].each_pair { |subkey, subdata|
|
765
|
+
next if !AMROOT and subdata['rootonly']
|
766
|
+
print " "+subdata["#menu"].bold+". "+subdata["title"]
|
767
|
+
printVal(subdata)
|
768
|
+
puts ""
|
769
|
+
}
|
770
|
+
end
|
600
771
|
else
|
601
772
|
printVal(data)
|
602
773
|
puts ""
|
@@ -612,7 +783,7 @@ trap("INT"){ puts "" ; exit }
|
|
612
783
|
importCurrentValues if !$INITIALIZE or $HAVE_GLOBAL_CONFIG
|
613
784
|
importCLIValues
|
614
785
|
setDefaults
|
615
|
-
assignMenuEntries # populates
|
786
|
+
assignMenuEntries # populates $MENU_MAP
|
616
787
|
|
617
788
|
def ask(desc)
|
618
789
|
puts ""
|
@@ -628,7 +799,7 @@ def ask(desc)
|
|
628
799
|
Readline.redisplay
|
629
800
|
Readline.pre_input_hook = nil
|
630
801
|
end
|
631
|
-
|
802
|
+
end
|
632
803
|
val = Readline.readline(prompt, false)
|
633
804
|
if desc['array'] and !val.nil?
|
634
805
|
val = val.strip.split(/\s*,\s*/)
|
@@ -642,14 +813,18 @@ def ask(desc)
|
|
642
813
|
val
|
643
814
|
end
|
644
815
|
|
645
|
-
def validate(newval, reqs, addnewline = true)
|
816
|
+
def validate(newval, reqs, addnewline = true, in_use: [])
|
646
817
|
ok = true
|
647
|
-
def validate_individual_value(newval, reqs, addnewline)
|
818
|
+
def validate_individual_value(newval, reqs, addnewline, in_use: [])
|
648
819
|
ok = true
|
649
820
|
if reqs['boolean'] and newval != true and newval != false and newval != nil
|
650
821
|
puts "\nInvalid value '#{newval.bold}' for #{reqs['title'].bold} (must be true or false)".light_red.on_black
|
651
822
|
puts "\n\n" if addnewline
|
652
823
|
ok = false
|
824
|
+
elsif in_use.size > 0 and in_use.include?(newval)
|
825
|
+
puts "\n##{reqs['title'].bold} #{newval} not available".light_red.on_black
|
826
|
+
puts "\n\n" if addnewline
|
827
|
+
ok = false
|
653
828
|
elsif reqs['pattern']
|
654
829
|
if newval.nil?
|
655
830
|
puts "\nSupplied value for #{reqs['title'].bold} did not pass validation".light_red.on_black
|
@@ -677,11 +852,11 @@ def validate(newval, reqs, addnewline = true)
|
|
677
852
|
ok = false
|
678
853
|
else
|
679
854
|
newval.each { |v|
|
680
|
-
ok = false if !validate_individual_value(v, reqs, addnewline)
|
855
|
+
ok = false if !validate_individual_value(v, reqs, addnewline, in_use: in_use)
|
681
856
|
}
|
682
857
|
end
|
683
858
|
else
|
684
|
-
ok = false if !validate_individual_value(newval, reqs, addnewline)
|
859
|
+
ok = false if !validate_individual_value(newval, reqs, addnewline, in_use: in_use)
|
685
860
|
end
|
686
861
|
ok
|
687
862
|
end
|
@@ -707,11 +882,21 @@ def entireConfigValid?
|
|
707
882
|
ok
|
708
883
|
end
|
709
884
|
|
710
|
-
def
|
885
|
+
def generateMiniMenu(srctree)
|
886
|
+
map = {}
|
887
|
+
tree = cloneHash(srctree)
|
888
|
+
return [tree, map]
|
889
|
+
end
|
890
|
+
|
891
|
+
def menu(tree = $CONFIGURABLES, map = $MENU_MAP, submenu_name = nil, in_use_names = [])
|
711
892
|
begin
|
712
|
-
optlist = displayCurrentOpts
|
893
|
+
optlist = displayCurrentOpts(tree)
|
713
894
|
begin
|
714
|
-
|
895
|
+
if submenu_name
|
896
|
+
print "Enter an option to change, "+"O".bold+" to save #{submenu_name.bold}, or "+"q".bold+" to return.\n> "
|
897
|
+
else
|
898
|
+
print "Enter an option to change, "+"O".bold+" to save this config, or "+"^D".bold+" to quit.\n> "
|
899
|
+
end
|
715
900
|
answer = gets
|
716
901
|
if answer.nil?
|
717
902
|
puts ""
|
@@ -722,23 +907,69 @@ def menu
|
|
722
907
|
puts ""
|
723
908
|
exit 0
|
724
909
|
end
|
725
|
-
|
726
|
-
|
727
|
-
|
910
|
+
|
911
|
+
if map.has_key?(answer) and map[answer]["#addnew"]
|
912
|
+
minimap = {}
|
913
|
+
assignMenuEntries(map[answer], minimap)
|
914
|
+
newtree, newmap = menu(
|
915
|
+
map[answer],
|
916
|
+
minimap,
|
917
|
+
map[answer]['#title']+" (NEW)",
|
918
|
+
map[answer]['#entries'].keys.reject { |k| k.match(/^#/) }
|
919
|
+
)
|
920
|
+
if newtree
|
921
|
+
newname = newtree["name"]["value"]
|
922
|
+
newtree.delete("#addnew")
|
923
|
+
parentname = map[answer]['#key']
|
924
|
+
|
925
|
+
tree[parentname]['subtree'] ||= {}
|
926
|
+
tree[parentname]['subtree']['#entries'] ||= {}
|
927
|
+
# if we're in cloud land and just added a 2nd entry, set the original
|
928
|
+
# one to 'default'
|
929
|
+
if tree[parentname]['subtree']['#entries'].size == 1
|
930
|
+
end
|
931
|
+
tree[parentname]['subtree']['#entries'][newname] = cloneHash(newtree)
|
932
|
+
|
933
|
+
map = {} # rebuild the menu map to include new entries
|
934
|
+
assignMenuEntries(tree, map)
|
935
|
+
end
|
936
|
+
# exit
|
937
|
+
# map[answer] = newtree if newtree
|
938
|
+
elsif map.has_key?(answer) and map[answer]["is_submenu"]
|
939
|
+
minimap = {}
|
940
|
+
parentname = map[answer]['#key']
|
941
|
+
entryname = map[answer]['#title']
|
942
|
+
puts PP.pp(map[answer], '').yellow
|
943
|
+
puts PP.pp(tree[parentname]['subtree']['#entries'][entryname], '').red
|
944
|
+
assignMenuEntries(tree[parentname]['subtree']['#entries'][entryname], minimap)
|
945
|
+
newtree, newmap = menu(
|
946
|
+
map[answer],
|
947
|
+
minimap,
|
948
|
+
map[answer]["#title"],
|
949
|
+
(map[answer]['#entries'].keys - [map[answer]['#title']])
|
950
|
+
)
|
951
|
+
map[answer] = newtree if newtree
|
952
|
+
elsif map.has_key?(answer) and !map[answer].has_key?("subtree")
|
953
|
+
newval = ask(map[answer])
|
954
|
+
if !validate(newval, map[answer], in_use: in_use_names)
|
728
955
|
sleep 1
|
729
956
|
next
|
730
957
|
end
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
958
|
+
map[answer]['value'] = newval == "" ? nil : newval
|
959
|
+
tree[map[answer]['#key']]['value'] = newval
|
960
|
+
$CHANGES.concat(map[answer]['changes']) if map[answer].include?("changes")
|
961
|
+
if map[answer]['title'] == "Local Hostname"
|
962
|
+
# $CONFIGURABLES["aws"]["subtree"]["log_bucket_name"]["default"] = newval
|
963
|
+
# $CONFIGURABLES["google"]["subtree"]["log_bucket_name"]["default"] = newval
|
964
|
+
elsif map[answer]['title'] == "Public Address"
|
736
965
|
$CONFIGURABLES["banner"]["default"] = "Mu Master at #{newval}"
|
737
|
-
elsif
|
966
|
+
elsif map[answer]['title'] == "Mu Admin Email"
|
738
967
|
$CONFIGURABLES["jenkins"]["subtree"]["admin_email"]["default"] = newval
|
739
968
|
end
|
740
969
|
changed = true
|
741
970
|
puts ""
|
971
|
+
elsif ["q", "Q"].include?(answer)
|
972
|
+
return nil
|
742
973
|
elsif !["", "0", "O", "o"].include?(answer)
|
743
974
|
puts "\nInvalid option '#{answer.bold}'".light_red.on_black+"\n\n"
|
744
975
|
sleep 1
|
@@ -746,10 +977,13 @@ def menu
|
|
746
977
|
answer = nil if !entireConfigValid?
|
747
978
|
end
|
748
979
|
end while answer != "0" and answer != "O" and answer != "o"
|
980
|
+
|
981
|
+
return [tree, map]
|
749
982
|
end
|
750
983
|
|
751
984
|
if !$opts[:noninteractive]
|
752
|
-
menu
|
985
|
+
$CONFIGURABLES, $MENU_MAP = menu
|
986
|
+
$MU_CFG = setConfigTree
|
753
987
|
else
|
754
988
|
if !entireConfigValid?
|
755
989
|
puts "Configuration had validation errors, exiting.\nRe-invoke #{$0} to correct."
|
@@ -758,8 +992,11 @@ else
|
|
758
992
|
end
|
759
993
|
|
760
994
|
if AMROOT
|
761
|
-
|
762
|
-
|
995
|
+
require File.realpath(File.expand_path(File.dirname(__FILE__)+"/mu-load-config.rb"))
|
996
|
+
newcfg = cloneHash($MU_CFG)
|
997
|
+
newcfg['multiuser'] = true
|
998
|
+
saveMuConfig(newcfg)
|
999
|
+
$MU_CFG = loadMuConfig($MU_SET_DEFAULTS)
|
763
1000
|
end
|
764
1001
|
|
765
1002
|
def set389DSCreds
|
@@ -820,8 +1057,8 @@ end
|
|
820
1057
|
if AMROOT
|
821
1058
|
cur_chef_version = `/bin/rpm -q chef`.sub(/^chef-(\d+\.\d+\.\d+-\d+)\..*/, '\1').chomp
|
822
1059
|
pref_chef_version = File.read("#{MU_BASE}/var/mu-chef-client-version").chomp
|
823
|
-
if cur_chef_version != pref_chef_version
|
824
|
-
puts "Updating MU-MASTER's Chef Client to '#{pref_chef_version}'"
|
1060
|
+
if (cur_chef_version != pref_chef_version and cur_chef_version.sub(/\-\d+$/, "") != pref_chef_version) or cur_chef_version.match(/is not installed/)
|
1061
|
+
puts "Updating MU-MASTER's Chef Client to '#{pref_chef_version}' from '#{cur_chef_version}'"
|
825
1062
|
chef_installer = open("https://omnitruck.chef.io/install.sh").read
|
826
1063
|
File.open("#{HOMEDIR}/chef-install.sh", File::CREAT|File::TRUNC|File::RDWR, 0644){ |f|
|
827
1064
|
f.puts chef_installer
|
@@ -1063,7 +1300,7 @@ MU.log "Verifying MU-MASTER's Chef run list", MU::NOTICE
|
|
1063
1300
|
MU::Groomer::Chef.loadChefLib
|
1064
1301
|
chef_node = ::Chef::Node.load("MU-MASTER")
|
1065
1302
|
run_list = ["role[mu-master]"]
|
1066
|
-
run_list << "role[mu-master-jenkins]" if $MU_CFG['jenkins']['enable']
|
1303
|
+
run_list << "role[mu-master-jenkins]" if $MU_CFG['jenkins'] and $MU_CFG['jenkins']['enable']
|
1067
1304
|
run_list.concat($MU_CFG['master_runlist_extras']) if $MU_CFG['master_runlist_extras'].is_a?(Array)
|
1068
1305
|
set_runlist = false
|
1069
1306
|
run_list.each { |rl|
|