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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile +16 -54
  3. data/Berksfile.lock +14 -62
  4. data/bin/mu-aws-setup +131 -108
  5. data/bin/mu-configure +311 -74
  6. data/bin/mu-gcp-setup +84 -62
  7. data/bin/mu-load-config.rb +46 -2
  8. data/bin/mu-self-update +11 -9
  9. data/bin/mu-upload-chef-artifacts +4 -4
  10. data/{mu.gemspec → cloud-mu.gemspec} +2 -2
  11. data/cookbooks/awscli/Berksfile +8 -0
  12. data/cookbooks/mu-activedirectory/Berksfile +11 -0
  13. data/cookbooks/mu-firewall/Berksfile +9 -0
  14. data/cookbooks/mu-firewall/metadata.rb +1 -1
  15. data/cookbooks/mu-glusterfs/Berksfile +10 -0
  16. data/cookbooks/mu-jenkins/Berksfile +14 -0
  17. data/cookbooks/mu-master/Berksfile +23 -0
  18. data/cookbooks/mu-master/attributes/default.rb +1 -1
  19. data/cookbooks/mu-master/metadata.rb +2 -2
  20. data/cookbooks/mu-master/recipes/default.rb +1 -1
  21. data/cookbooks/mu-master/recipes/init.rb +7 -3
  22. data/cookbooks/mu-master/recipes/ssl-certs.rb +1 -0
  23. data/cookbooks/mu-mongo/Berksfile +10 -0
  24. data/cookbooks/mu-openvpn/Berksfile +11 -0
  25. data/cookbooks/mu-php54/Berksfile +13 -0
  26. data/cookbooks/mu-splunk/Berksfile +10 -0
  27. data/cookbooks/mu-tools/Berksfile +21 -0
  28. data/cookbooks/mu-tools/files/default/Mu_CA.pem +15 -15
  29. data/cookbooks/mu-utility/Berksfile +9 -0
  30. data/cookbooks/mu-utility/metadata.rb +2 -1
  31. data/cookbooks/nagios/Berksfile +7 -4
  32. data/cookbooks/s3fs/Berksfile +9 -0
  33. data/environments/dev.json +6 -6
  34. data/environments/prod.json +6 -6
  35. data/modules/mu.rb +20 -42
  36. data/modules/mu/cleanup.rb +102 -100
  37. data/modules/mu/cloud.rb +90 -28
  38. data/modules/mu/clouds/aws.rb +449 -218
  39. data/modules/mu/clouds/aws/alarm.rb +29 -17
  40. data/modules/mu/clouds/aws/cache_cluster.rb +78 -64
  41. data/modules/mu/clouds/aws/collection.rb +25 -18
  42. data/modules/mu/clouds/aws/container_cluster.rb +73 -66
  43. data/modules/mu/clouds/aws/database.rb +124 -116
  44. data/modules/mu/clouds/aws/dnszone.rb +27 -20
  45. data/modules/mu/clouds/aws/firewall_rule.rb +30 -22
  46. data/modules/mu/clouds/aws/folder.rb +18 -3
  47. data/modules/mu/clouds/aws/function.rb +77 -23
  48. data/modules/mu/clouds/aws/group.rb +19 -12
  49. data/modules/mu/clouds/aws/habitat.rb +153 -0
  50. data/modules/mu/clouds/aws/loadbalancer.rb +59 -52
  51. data/modules/mu/clouds/aws/log.rb +30 -23
  52. data/modules/mu/clouds/aws/msg_queue.rb +29 -20
  53. data/modules/mu/clouds/aws/notifier.rb +222 -0
  54. data/modules/mu/clouds/aws/role.rb +178 -90
  55. data/modules/mu/clouds/aws/search_domain.rb +40 -24
  56. data/modules/mu/clouds/aws/server.rb +169 -137
  57. data/modules/mu/clouds/aws/server_pool.rb +60 -83
  58. data/modules/mu/clouds/aws/storage_pool.rb +59 -31
  59. data/modules/mu/clouds/aws/user.rb +36 -27
  60. data/modules/mu/clouds/aws/userdata/linux.erb +101 -93
  61. data/modules/mu/clouds/aws/vpc.rb +250 -189
  62. data/modules/mu/clouds/azure.rb +132 -0
  63. data/modules/mu/clouds/cloudformation.rb +65 -1
  64. data/modules/mu/clouds/cloudformation/alarm.rb +8 -0
  65. data/modules/mu/clouds/cloudformation/cache_cluster.rb +7 -0
  66. data/modules/mu/clouds/cloudformation/collection.rb +7 -0
  67. data/modules/mu/clouds/cloudformation/database.rb +7 -0
  68. data/modules/mu/clouds/cloudformation/dnszone.rb +7 -0
  69. data/modules/mu/clouds/cloudformation/firewall_rule.rb +9 -2
  70. data/modules/mu/clouds/cloudformation/loadbalancer.rb +7 -0
  71. data/modules/mu/clouds/cloudformation/log.rb +7 -0
  72. data/modules/mu/clouds/cloudformation/server.rb +7 -0
  73. data/modules/mu/clouds/cloudformation/server_pool.rb +7 -0
  74. data/modules/mu/clouds/cloudformation/vpc.rb +7 -0
  75. data/modules/mu/clouds/google.rb +214 -110
  76. data/modules/mu/clouds/google/container_cluster.rb +42 -24
  77. data/modules/mu/clouds/google/database.rb +15 -6
  78. data/modules/mu/clouds/google/firewall_rule.rb +17 -25
  79. data/modules/mu/clouds/google/group.rb +13 -5
  80. data/modules/mu/clouds/google/habitat.rb +105 -0
  81. data/modules/mu/clouds/google/loadbalancer.rb +28 -20
  82. data/modules/mu/clouds/google/server.rb +93 -354
  83. data/modules/mu/clouds/google/server_pool.rb +18 -10
  84. data/modules/mu/clouds/google/user.rb +22 -14
  85. data/modules/mu/clouds/google/vpc.rb +97 -69
  86. data/modules/mu/config.rb +133 -38
  87. data/modules/mu/config/alarm.rb +25 -0
  88. data/modules/mu/config/cache_cluster.rb +5 -3
  89. data/modules/mu/config/cache_cluster.yml +23 -0
  90. data/modules/mu/config/database.rb +25 -16
  91. data/modules/mu/config/database.yml +3 -3
  92. data/modules/mu/config/function.rb +1 -2
  93. data/modules/mu/config/{project.rb → habitat.rb} +10 -10
  94. data/modules/mu/config/notifier.rb +85 -0
  95. data/modules/mu/config/notifier.yml +9 -0
  96. data/modules/mu/config/role.rb +1 -1
  97. data/modules/mu/config/search_domain.yml +2 -2
  98. data/modules/mu/config/server.rb +13 -1
  99. data/modules/mu/config/server.yml +3 -3
  100. data/modules/mu/config/server_pool.rb +3 -1
  101. data/modules/mu/config/storage_pool.rb +3 -1
  102. data/modules/mu/config/storage_pool.yml +19 -0
  103. data/modules/mu/config/vpc.rb +70 -8
  104. data/modules/mu/groomers/chef.rb +2 -3
  105. data/modules/mu/kittens.rb +500 -122
  106. data/modules/mu/master.rb +5 -5
  107. data/modules/mu/mommacat.rb +151 -91
  108. data/modules/tests/super_complex_bok.yml +12 -0
  109. data/modules/tests/super_simple_bok.yml +12 -0
  110. data/spec/mu/clouds/azure_spec.rb +82 -0
  111. data/spec/spec_helper.rb +105 -0
  112. metadata +26 -5
  113. data/modules/mu/clouds/aws/notification.rb +0 -139
  114. data/modules/mu/config/notification.rb +0 -44
@@ -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 Number",
133
- "desc" => "Account number for the Amazon Web Services account which we administer",
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 we operate"
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
- $CONFIGURABLES.each_pair { |key, data|
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["subtree"].each_pair { |subkey, subdata|
296
- next if !AMROOT and subdata['rootonly']
297
- $CONFIGURABLES[key]["subtree"][subkey]["menu"] = count.to_s+letters[lettercount]
298
- $MENU_MAP[count.to_s+letters[lettercount]] = $CONFIGURABLES[key]["subtree"][subkey]
299
- lettercount = lettercount + 1
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
- $MENU_MAP[count.to_s] = $CONFIGURABLES[key]
303
- $CONFIGURABLES[key]["menu"] = count.to_s
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
- $MENU_MAP.freeze
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["subtree"].each_pair { |subkey, subdata|
510
- next if !AMROOT and subdata['rootonly']
511
- if $opts[(subdata['cli-opt'].gsub(/-/, "_")+"_given").to_sym]
512
- newval = runValueCallback(subdata, $opts[subdata['cli-opt'].gsub(/-/, "_").to_sym])
513
- subdata["value"] = newval if !newval.nil?
514
- $CHANGES.concat(subdata['changes']) if subdata['changes']
515
- end
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]["subtree"].keys.each { |subkey|
536
- next if !$MU_CFG[key].has_key?(subkey)
537
- $CONFIGURABLES[key]["subtree"][subkey]["value"] = $MU_CFG[key][subkey]
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
- if !valid
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
- $CONFIGURABLES.each_pair { |key, data|
708
+ tree.each_pair { |key, data|
565
709
  next if !AMROOT and data['rootonly']
566
710
  if data.has_key?("subtree")
567
- data["subtree"].each_pair { |subkey, subdata|
568
- next if !AMROOT and subdata['rootonly']
569
- if !subdata["value"].nil?
570
- cfg[key] ||= {}
571
- cfg[key][subkey] = subdata["value"]
572
- elsif !subdata["default"].nil? and !$HAVE_GLOBAL_CONFIG or ($MU_CFG and (!$MU_CFG[key] or !$MU_CFG[key][subkey]))
573
- cfg[key] ||= {}
574
- cfg[key][subkey] = subdata["default"]
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
- $CONFIGURABLES.each_pair { |key, data|
747
+ tree.each_pair { |key, data|
590
748
  next if !AMROOT and data['rootonly']
591
- print data["menu"].bold+") "+data["title"]
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["subtree"].each_pair { |subkey, subdata|
595
- next if !AMROOT and subdata['rootonly']
596
- print " "+subdata["menu"].bold+". "+subdata["title"]
597
- printVal(subdata)
598
- puts ""
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 and freezes $MENU_MAP
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
- end
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 menu
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
- print "Enter an option to change, "+"O".bold+" to save this config, or "+"^D".bold+" to quit.\n> "
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
- if $MENU_MAP.has_key?(answer) and !$MENU_MAP[answer].has_key?("subtree")
726
- newval = ask($MENU_MAP[answer])
727
- if !validate(newval, $MENU_MAP[answer])
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
- $MENU_MAP[answer]['value'] = newval == "" ? nil : newval
732
- $CHANGES.concat($MENU_MAP[answer]['changes']) if $MENU_MAP[answer].include?("changes")
733
- if $MENU_MAP[answer]['title'] == "Local Hostname"
734
- $CONFIGURABLES["aws"]["subtree"]["log_bucket_name"]["default"] = newval
735
- elsif $MENU_MAP[answer]['title'] == "Public Address"
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 $MENU_MAP[answer]['title'] == "Mu Admin Email"
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
- $MU_CFG['multiuser'] = true
762
- saveMuConfig($MU_CFG)
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|