cloud-mu 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +12 -1
  4. data/bin/mu-load-config.rb +2 -1
  5. data/bin/mu-run-tests +14 -2
  6. data/cloud-mu.gemspec +3 -3
  7. data/modules/mu.rb +2 -2
  8. data/modules/mu/adoption.rb +5 -5
  9. data/modules/mu/cleanup.rb +47 -25
  10. data/modules/mu/cloud.rb +29 -1
  11. data/modules/mu/cloud/dnszone.rb +0 -2
  12. data/modules/mu/cloud/resource_base.rb +9 -3
  13. data/modules/mu/cloud/wrappers.rb +4 -0
  14. data/modules/mu/config.rb +1 -1
  15. data/modules/mu/config/bucket.rb +31 -2
  16. data/modules/mu/config/cache_cluster.rb +1 -1
  17. data/modules/mu/config/cdn.rb +100 -0
  18. data/modules/mu/config/container_cluster.rb +1 -1
  19. data/modules/mu/config/database.rb +1 -1
  20. data/modules/mu/config/dnszone.rb +4 -3
  21. data/modules/mu/config/endpoint.rb +1 -0
  22. data/modules/mu/config/function.rb +16 -7
  23. data/modules/mu/config/job.rb +89 -0
  24. data/modules/mu/config/notifier.rb +7 -18
  25. data/modules/mu/config/ref.rb +53 -7
  26. data/modules/mu/config/server.rb +1 -1
  27. data/modules/mu/config/vpc.rb +1 -0
  28. data/modules/mu/defaults/AWS.yaml +26 -26
  29. data/modules/mu/deploy.rb +13 -0
  30. data/modules/mu/master.rb +21 -0
  31. data/modules/mu/mommacat.rb +1 -0
  32. data/modules/mu/mommacat/daemon.rb +13 -7
  33. data/modules/mu/providers/aws.rb +115 -16
  34. data/modules/mu/providers/aws/alarm.rb +2 -2
  35. data/modules/mu/providers/aws/bucket.rb +274 -40
  36. data/modules/mu/providers/aws/cache_cluster.rb +4 -4
  37. data/modules/mu/providers/aws/cdn.rb +782 -0
  38. data/modules/mu/providers/aws/collection.rb +2 -2
  39. data/modules/mu/providers/aws/container_cluster.rb +57 -37
  40. data/modules/mu/providers/aws/database.rb +11 -11
  41. data/modules/mu/providers/aws/dnszone.rb +24 -7
  42. data/modules/mu/providers/aws/endpoint.rb +535 -50
  43. data/modules/mu/providers/aws/firewall_rule.rb +6 -3
  44. data/modules/mu/providers/aws/folder.rb +1 -1
  45. data/modules/mu/providers/aws/function.rb +288 -125
  46. data/modules/mu/providers/aws/group.rb +9 -7
  47. data/modules/mu/providers/aws/habitat.rb +2 -2
  48. data/modules/mu/providers/aws/job.rb +466 -0
  49. data/modules/mu/providers/aws/loadbalancer.rb +9 -8
  50. data/modules/mu/providers/aws/log.rb +3 -3
  51. data/modules/mu/providers/aws/msg_queue.rb +12 -3
  52. data/modules/mu/providers/aws/nosqldb.rb +96 -5
  53. data/modules/mu/providers/aws/notifier.rb +135 -63
  54. data/modules/mu/providers/aws/role.rb +51 -37
  55. data/modules/mu/providers/aws/search_domain.rb +165 -29
  56. data/modules/mu/providers/aws/server.rb +12 -9
  57. data/modules/mu/providers/aws/server_pool.rb +26 -13
  58. data/modules/mu/providers/aws/storage_pool.rb +2 -2
  59. data/modules/mu/providers/aws/user.rb +4 -4
  60. data/modules/mu/providers/aws/userdata/linux.erb +5 -4
  61. data/modules/mu/providers/aws/vpc.rb +3 -3
  62. data/modules/mu/providers/azure/server.rb +2 -1
  63. data/modules/mu/providers/google.rb +1 -0
  64. data/modules/mu/providers/google/bucket.rb +1 -1
  65. data/modules/mu/providers/google/container_cluster.rb +1 -1
  66. data/modules/mu/providers/google/database.rb +1 -1
  67. data/modules/mu/providers/google/firewall_rule.rb +1 -1
  68. data/modules/mu/providers/google/folder.rb +1 -1
  69. data/modules/mu/providers/google/function.rb +1 -1
  70. data/modules/mu/providers/google/group.rb +1 -1
  71. data/modules/mu/providers/google/habitat.rb +1 -1
  72. data/modules/mu/providers/google/loadbalancer.rb +1 -1
  73. data/modules/mu/providers/google/role.rb +4 -2
  74. data/modules/mu/providers/google/server.rb +1 -1
  75. data/modules/mu/providers/google/server_pool.rb +1 -1
  76. data/modules/mu/providers/google/user.rb +1 -1
  77. data/modules/mu/providers/google/vpc.rb +1 -1
  78. data/modules/tests/aws-jobs-functions.yaml +46 -0
  79. data/modules/tests/centos6.yaml +4 -0
  80. data/modules/tests/centos7.yaml +4 -0
  81. data/modules/tests/ecs.yaml +2 -2
  82. data/modules/tests/eks.yaml +1 -1
  83. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  84. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  85. data/modules/tests/microservice_app.yaml +288 -0
  86. data/modules/tests/rds.yaml +5 -5
  87. data/modules/tests/regrooms/rds.yaml +5 -5
  88. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  89. data/modules/tests/super_complex_bok.yml +2 -2
  90. data/modules/tests/super_simple_bok.yml +2 -2
  91. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd9a15a0e94a578919c0cb22bf5c95ee86b5b4a03fcc0ea1c35da2c161e8cf31
4
- data.tar.gz: 04cdc9d9a70de97fdaf12021166b707921ec981d7ab2dc92cd1ebce491a9e130
3
+ metadata.gz: 87070670dfd1950848c17f897f49f087ada7a3155e3a5ef8e24ffb2c62583e7f
4
+ data.tar.gz: 8cee858ebde553ee022e56db98e90c62a28eb4a0b0247f74bd48a940956fe1c5
5
5
  SHA512:
6
- metadata.gz: adfef4f231a946a3929b7f8026a9e338658dadfef3482942a46e8fdb9b10c4ca7d68aa457d135815a3878882d88a103ec1ebacea4bcb1ae4c45b070d85ceee06
7
- data.tar.gz: 3a7b04d7d2486b05e8d413b78029b610bb7ba6bd8a33a1c24cf7b8bdf63dabaf9e0a1baab8408a66f995d05df719261248cbf27ee5b230a350109053ddb0420a
6
+ metadata.gz: 5ed8ad1fd6834f0b251079eb7d8b3f1805b0f88aa47486943f4bb7ad75196161c6c41a8e53364f755d46fe7fa6e51b8fb83ab518747369d07cdb0cb360689866
7
+ data.tar.gz: 53acff0bb1ea78fada8a9d8e6b3084ccb02209b42a1b19f99a80d792706cb55f1a2d5e97b48ee1fa54cc8b2ee8f7947feecd7a1465f24e18f9cdb86a499de8fa
data/Dockerfile CHANGED
@@ -8,7 +8,7 @@ RUN df -h
8
8
 
9
9
  RUN apt-get update
10
10
 
11
- RUN apt-get install -y ruby2.5-dev dnsutils ansible build-essential python-pip curl
11
+ RUN apt-get install -y ruby2.5-dev dnsutils ansible build-essential python-pip curl openssh-client
12
12
 
13
13
  RUN apt-get upgrade -y
14
14
 
@@ -45,6 +45,7 @@ $opt = Optimist::options do
45
45
  opt :habitats, "Limit scope of searches to the named accounts/projects/subscriptions, instead of search all habitats visible to our credentials.", :required => false, :type => :strings
46
46
  opt :regions, "Restrict to operating on a subset of available regions, instead of all that we know about.", :require => false, :type => :strings
47
47
  opt :scrub, "Whether to set scrub_mu_isms in the BoKs we generate", :default => $MU_CFG.has_key?('adopt_scrub_mu_isms') ? $MU_CFG['adopt_scrub_mu_isms'] : false
48
+ opt :pattern, "Only adopt resources whose resource name would match this pattern. Must be a valid regular expression. Alphabetical characters will be treated case-insensitively.", :required => false, :type => :string
48
49
  end
49
50
 
50
51
  ok = true
@@ -60,6 +61,16 @@ if $opt[:diff]
60
61
  $opt[:savedeploys] = false
61
62
  end
62
63
 
64
+ pattern = nil
65
+ if $opt[:pattern]
66
+ begin
67
+ pattern = Regexp.new($opt[:pattern], true)
68
+ rescue RegexpError => e
69
+ MU.log "Invalid --pattern option: #{e.message}", MU::ERR
70
+ exit 1
71
+ end
72
+ end
73
+
63
74
  types = []
64
75
  $opt[:types].each { |t|
65
76
  t_name = t.gsub(/-/, "_")
@@ -98,7 +109,7 @@ if !ok
98
109
  exit 1
99
110
  end
100
111
 
101
- adoption = MU::Adoption.new(clouds: clouds, types: types, parent: $opt[:parent], billing: $opt[:billing], sources: $opt[:sources], credentials: $opt[:credentials], group_by: $opt[:grouping].to_sym, savedeploys: $opt[:savedeploys], diff: $opt[:diff], habitats: $opt[:habitats], scrub_mu_isms: $opt[:scrub], regions: $opt[:regions], merge: $opt[:merge_changes])
112
+ adoption = MU::Adoption.new(clouds: clouds, types: types, parent: $opt[:parent], billing: $opt[:billing], sources: $opt[:sources], credentials: $opt[:credentials], group_by: $opt[:grouping].to_sym, savedeploys: $opt[:savedeploys], diff: $opt[:diff], habitats: $opt[:habitats], scrub_mu_isms: $opt[:scrub], regions: $opt[:regions], merge: $opt[:merge_changes], pattern: pattern)
102
113
  found = adoption.scrapeClouds
103
114
  if found.nil? or found.empty?
104
115
  MU.log "No resources found to adopt", MU::WARN, details: {"clouds" => clouds, "types" => types }
@@ -134,7 +134,7 @@ def loadMuConfig(default_cfg_overrides = nil)
134
134
  }
135
135
  end
136
136
 
137
- global_cfg = { "config_files" => [] }
137
+ global_cfg = { "config_files" => [], "overridden_keys" => [] }
138
138
  if File.exist?(cfgPath)
139
139
  global_cfg = YAML.load(File.read(cfgPath))
140
140
  global_cfg["config_files"] = [cfgPath]
@@ -147,6 +147,7 @@ def loadMuConfig(default_cfg_overrides = nil)
147
147
  if localfile
148
148
  global_cfg.merge!(localfile)
149
149
  global_cfg["config_files"] << "#{home}/.mu.yaml"
150
+ global_cfg["overridden_keys"] = localfile.keys
150
151
  end
151
152
  end
152
153
  if !global_cfg.has_key?("installdir")
@@ -34,6 +34,7 @@ Usage:
34
34
  #{$0} [-m <#>] [-f] [-v] [specific test BoK to run [...]]
35
35
  EOS
36
36
  opt :max_threads, "Environment to set on creation.", :require => false, :default => 3, :type => :integer
37
+ opt :max_retries, "Number of times to retry failed tests in --dryrun mode.", :require => false, :default => 2, :type => :integer
37
38
  opt :full, "Actually run deploys, instead of --dryrun", :require => false, :default => false
38
39
  opt :verbose, "Show more information while running", :require => false, :default => false
39
40
  end
@@ -121,8 +122,19 @@ def execCommand(cmd, results_stash)
121
122
  }
122
123
 
123
124
  ok = true
124
- output = %x{#{cmd} 2>&1}
125
- ok = false if $?.exitstatus != 0
125
+ retries = 0
126
+ begin
127
+ output = %x{#{cmd} 2>&1}
128
+ if $?.exitstatus != 0
129
+ ok = false
130
+ retries += 1
131
+ if $opts[:verbose] and !$opts[:full] and retries <= $opts[:max_retries]
132
+ puts "#{cmd} RETRY #{retries.to_s}".light_red
133
+ end
134
+ else
135
+ ok = true
136
+ end
137
+ end while !ok and !$opts[:full] and retries <= $opts[:max_retries]
126
138
 
127
139
  results_stash["output"] += output
128
140
 
@@ -17,8 +17,8 @@ end
17
17
 
18
18
  Gem::Specification.new do |s|
19
19
  s.name = 'cloud-mu'
20
- s.version = '3.2.0'
21
- s.date = '2020-06-16'
20
+ s.version = '3.3.0'
21
+ s.date = '2020-09-22'
22
22
  s.require_paths = ['modules']
23
23
  s.required_ruby_version = '>= 2.4'
24
24
  s.summary = "The eGTLabs Mu toolkit for unified cloud deployments"
@@ -57,7 +57,7 @@ EOF
57
57
  s.add_runtime_dependency 'rack', "~> 2.0"
58
58
  s.add_runtime_dependency 'ruby-graphviz', "~> 1.2"
59
59
  s.add_runtime_dependency 'rubocop', '~> 0.58'
60
- s.add_runtime_dependency 'rubyzip', "~> 2.0"
60
+ s.add_runtime_dependency 'rubyzip', "~> 2.3"
61
61
  s.add_runtime_dependency 'simple-password-gen', "~> 0.1"
62
62
  s.add_runtime_dependency 'slack-notifier', "~> 2.3"
63
63
  s.add_runtime_dependency 'solve', '~> 4.0'
@@ -299,8 +299,8 @@ module MU
299
299
  # Wrapper class for temporary Exceptions. Gives our internals something to
300
300
  # inherit that will log a notice message appropriately before bubbling up.
301
301
  class MuNonFatal < StandardError
302
- def initialize(message = nil, silent: false)
303
- MU.log message, MU::NOTICE if !message.nil? and !silent
302
+ def initialize(message = nil, silent: false, details: nil)
303
+ MU.log message, MU::NOTICE, details: details if !message.nil? and !silent
304
304
  if MU.verbosity == MU::Logger::SILENT
305
305
  super ""
306
306
  else
@@ -30,8 +30,7 @@ module MU
30
30
  :omnibus => "Jam everything into one monolothic configuration"
31
31
  }
32
32
 
33
-
34
- def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [], scrub_mu_isms: false, regions: [], merge: false)
33
+ def initialize(clouds: MU::Cloud.supportedClouds, types: MU::Cloud.resource_types.keys, parent: nil, billing: nil, sources: nil, credentials: nil, group_by: :logical, savedeploys: false, diff: false, habitats: [], scrub_mu_isms: false, regions: [], merge: false, pattern: nil)
35
34
  @scraped = {}
36
35
  @clouds = clouds
37
36
  @types = types
@@ -49,6 +48,7 @@ module MU
49
48
  @habitats ||= []
50
49
  @scrub_mu_isms = scrub_mu_isms
51
50
  @merge = merge
51
+ @pattern = pattern
52
52
  end
53
53
 
54
54
  # Walk cloud providers with available credentials to discover resources
@@ -127,6 +127,7 @@ module MU
127
127
  if obj.habitat and !cloudclass.listHabitats(credset).include?(obj.habitat)
128
128
  next
129
129
  end
130
+
130
131
  # XXX apply any filters (e.g. MU-ID tags)
131
132
  if obj.cloud_id.nil?
132
133
  MU.log "This damn thing gave me no cloud id, what do I even do with that", MU::ERR, details: obj
@@ -292,7 +293,7 @@ module MU
292
293
  start = Time.now
293
294
 
294
295
  kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats, types: @types)
295
- if kitten_cfg
296
+ if kitten_cfg and (!@pattern or @pattern.match(kitten_cfg['name']))
296
297
  print "."
297
298
  kitten_cfg.delete("credentials") if @target_creds
298
299
  class_semaphore.synchronize {
@@ -791,8 +792,7 @@ module MU
791
792
  elsif hashcfg["id"] and !hashcfg["name"]
792
793
  hashcfg.delete("deploy_id")
793
794
  else
794
- pp parent.cloud_desc
795
- raise Incomplete, "Failed to resolve reference on behalf of #{parent}"
795
+ raise Incomplete.new "Failed to resolve reference on behalf of #{parent}", details: hashcfg
796
796
  end
797
797
  hashcfg.delete("deploy_id") if hashcfg['deploy_id'] == deploy.deploy_id
798
798
 
@@ -32,7 +32,7 @@ module MU
32
32
 
33
33
  # Resource types, in the order in which we generally have to clean them up
34
34
  # to disentangle them from one another.
35
- TYPES_IN_ORDER = ["Collection", "Endpoint", "Function", "ServerPool", "ContainerCluster", "SearchDomain", "Server", "MsgQueue", "Database", "CacheCluster", "StoragePool", "LoadBalancer", "NoSQLDB", "FirewallRule", "Alarm", "Notifier", "Log", "VPC", "Role", "Group", "User", "Bucket", "DNSZone", "Collection"]
35
+ TYPES_IN_ORDER = ["Collection", "CDN", "Endpoint", "Function", "ServerPool", "ContainerCluster", "SearchDomain", "Server", "MsgQueue", "Database", "CacheCluster", "StoragePool", "LoadBalancer", "NoSQLDB", "FirewallRule", "Alarm", "Notifier", "Log", "Job", "VPC", "Role", "Group", "User", "Bucket", "DNSZone", "Collection"]
36
36
 
37
37
  # Purge all resources associated with a deployment.
38
38
  # @param deploy_id [String]: The identifier of the deployment to remove (typically seen in the MU-ID tag on a resource).
@@ -52,6 +52,7 @@ module MU
52
52
  @onlycloud = onlycloud
53
53
  @skipcloud = skipcloud
54
54
  @ignoremaster = ignoremaster
55
+ @deploy_id = deploy_id
55
56
 
56
57
  if @skipcloud and @onlycloud # you actually mean noop
57
58
  @onlycloud = @skipcloud = false
@@ -217,51 +218,69 @@ module MU
217
218
  cloudclass = MU::Cloud.cloudClass(cloud)
218
219
  habitatclass = MU::Cloud.resourceClass(cloud, "Habitat")
219
220
 
220
- projects = []
221
- if habitats
222
- projects = habitats
223
- else
221
+ if !habitats
222
+ habitats = []
224
223
  if $MU_CFG and $MU_CFG[cloud.downcase] and
225
224
  $MU_CFG[cloud.downcase][credset] and
226
225
  $MU_CFG[cloud.downcase][credset]["project"]
227
226
  # XXX GCP credential schema needs an array for projects
228
- projects << $MU_CFG[cloud.downcase][credset]["project"]
227
+ habitats << $MU_CFG[cloud.downcase][credset]["project"]
229
228
  end
230
229
  begin
231
- projects.concat(cloudclass.listHabitats(credset, use_cache: false))
230
+ habitats.concat(cloudclass.listHabitats(credset, use_cache: false))
232
231
  rescue NoMethodError
233
232
  end
234
233
  end
235
234
 
236
- if projects == []
237
- projects << "" # dummy
235
+ if habitats == []
236
+ habitats << "" # dummy
238
237
  MU.log "Checking for #{cloud}/#{credset} resources from #{MU.deploy_id} in #{region}", MU::NOTICE
239
238
  end
240
- projects.uniq!
239
+ habitats.uniq!
241
240
 
242
241
  # We do these in an order that unrolls dependent resources
243
242
  # sensibly, and we hit :Collection twice because AWS
244
243
  # CloudFormation sometimes fails internally.
245
- projectthreads = []
246
- projects.each { |project|
247
- if habitats and !habitats.empty? and project != ""
248
- next if !habitats.include?(project)
244
+ habitat_threads = []
245
+ habitats.each { |habitat|
246
+ if habitats and !habitats.empty? and habitat != ""
247
+ next if !habitats.include?(habitat)
249
248
  end
250
- if @habitatsused and !@habitatsused.empty? and project != ""
251
- next if !@habitatsused.include?(project)
249
+ if @habitatsused and !@habitatsused.empty? and habitat != ""
250
+ next if !@habitatsused.include?(habitat)
252
251
  end
253
- next if !habitatclass.isLive?(project, credset)
252
+ next if !habitatclass.isLive?(habitat, credset)
254
253
 
255
- projectthreads << Thread.new {
254
+ habitat_threads << Thread.new {
255
+ Thread.current.thread_variable_set("name", "#{cloud}/#{credset}/#{habitat}/#{region}")
256
256
  Thread.abort_on_exception = false
257
- if !cleanHabitat(cloud, credset, region, project, global_vs_region_semaphore, global_done)
257
+ if !cleanHabitat(cloud, credset, region, habitat, global_vs_region_semaphore, global_done)
258
258
  had_failures = true
259
259
  end
260
260
  } # TYPES_IN_ORDER.each { |t|
261
- } # projects.each { |project|
262
- projectthreads.each do |t|
263
- t.join
264
- end
261
+ } # habitats.each { |habitat|
262
+
263
+ last_checkin = Time.now
264
+ begin
265
+ deletia = []
266
+ habitat_threads.each { |t|
267
+ if !t.status
268
+ t.join
269
+ deletia << t
270
+ end
271
+ }
272
+ deletia.each { |t|
273
+ habitat_threads.delete(t)
274
+ }
275
+ if (Time.now - last_checkin) > 120
276
+ list = habitat_threads.map { |t|
277
+ t.thread_variable_get("name") + (t.thread_variable_get("type") ? "/"+t.thread_variable_get("type") : "")
278
+ }
279
+ MU.log "Waiting on #{habitat_threads.size.to_s} habitat#{habitat_threads.size > 1 ? "s" : ""} in region #{region}", MU::NOTICE, details: list
280
+ last_checkin = Time.now
281
+ end
282
+ sleep 10 if !habitat_threads.empty?
283
+ end while !habitat_threads.empty?
265
284
 
266
285
  had_failures
267
286
  end
@@ -311,7 +330,8 @@ module MU
311
330
  next
312
331
  end
313
332
  }
314
- had_failures = true
333
+
334
+ had_failures
315
335
  end
316
336
  private_class_method :cleanHabitat
317
337
 
@@ -322,6 +342,7 @@ module MU
322
342
  # @param flags [Hash]:
323
343
  # @param region [String]:
324
344
  def self.call_cleanup(type, credset, provider, flags, region)
345
+ Thread.current.thread_variable_set("type", type)
325
346
  if @mommacat.nil? or @mommacat.numKittens(types: [type]) > 0
326
347
  if @mommacat
327
348
 
@@ -344,7 +365,8 @@ module MU
344
365
  region: region,
345
366
  cloud: provider,
346
367
  flags: flags,
347
- credentials: credset
368
+ credentials: credset,
369
+ deploy_id: @deploy_id
348
370
  )
349
371
  else
350
372
  true
@@ -148,6 +148,12 @@ module MU
148
148
  # Stub base class; real implementations generated at runtime
149
149
  class NoSQLDB;
150
150
  end
151
+ # Stub base class; real implementations generated at runtime
152
+ class Job;
153
+ end
154
+ # Stub base class; real implementations generated at runtime
155
+ class CDN;
156
+ end
151
157
 
152
158
  # Denotes a resource implementation which is missing significant
153
159
  # functionality or is largely untested.
@@ -436,7 +442,29 @@ module MU
436
442
  :cfg_plural => "nosqldbs",
437
443
  :interface => self.const_get("NoSQLDB"),
438
444
  :deps_wait_on_my_creation => true,
439
- :waits_on_parent_completion => true,
445
+ :waits_on_parent_completion => false,
446
+ :class => @@generic_class_methods,
447
+ :instance => @@generic_instance_methods + [:groom]
448
+ },
449
+ :Job => {
450
+ :has_multiples => false,
451
+ :can_live_in_vpc => false,
452
+ :cfg_name => "job",
453
+ :cfg_plural => "jobs",
454
+ :interface => self.const_get("Job"),
455
+ :deps_wait_on_my_creation => true,
456
+ :waits_on_parent_completion => false,
457
+ :class => @@generic_class_methods,
458
+ :instance => @@generic_instance_methods + [:groom]
459
+ },
460
+ :CDN => {
461
+ :has_multiples => false,
462
+ :can_live_in_vpc => false,
463
+ :cfg_name => "cdn",
464
+ :cfg_plural => "cdns",
465
+ :interface => self.const_get("CDN"),
466
+ :deps_wait_on_my_creation => true,
467
+ :waits_on_parent_completion => false,
440
468
  :class => @@generic_class_methods,
441
469
  :instance => @@generic_instance_methods + [:groom]
442
470
  }
@@ -29,8 +29,6 @@ module MU
29
29
 
30
30
  # Wrapper for {MU::Cloud::AWS::DNSZone.manageRecord}. Spawns threads to create all
31
31
  # requested records in background and returns immediately.
32
- # @param cfg [Array]: An array of parsed {MU::Config::BasketofKittens::dnszones::records} objects.
33
- # @param target [String]: Optional target for the records to be created. Overrides targets embedded in cfg records.
34
32
  def self.createRecordsFromConfig(*flags)
35
33
  cloudclass = MU::Cloud.resourceClass(MU::Config.defaultCloud, "DNSZone")
36
34
  if !flags.nil? and flags.size == 1
@@ -227,6 +227,10 @@ module MU
227
227
  }
228
228
  end
229
229
 
230
+ MU::MommaCat.listOptionalTags.each_pair { |k, v|
231
+ @tags[k] ||= v if v
232
+ }
233
+
230
234
  if @cloudparentclass.respond_to?(:resourceInitHook)
231
235
  @cloudparentclass.resourceInitHook(self, @deploy)
232
236
  end
@@ -265,6 +269,7 @@ module MU
265
269
  attr_accessor :mu_windows_name # XXX might be ok as reader now
266
270
  end
267
271
  end
272
+ @tags["Name"] ||= @mu_name if @mu_name
268
273
  end
269
274
 
270
275
  end
@@ -893,10 +898,11 @@ module MU
893
898
  elsif method == :notify
894
899
  if retval.nil?
895
900
  MU.log self.to_s+" didn't return any metadata from notify", MU::WARN, details: @cloudobj.cloud_desc
901
+ else
902
+ retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
903
+ retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
904
+ @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
896
905
  end
897
- retval['cloud_id'] = @cloudobj.cloud_id.to_s if !@cloudobj.cloud_id.nil?
898
- retval['mu_name'] = @cloudobj.mu_name if !@cloudobj.mu_name.nil?
899
- @deploy.notify(self.class.cfg_plural, @config['name'], retval, triggering_node: @cloudobj, delayed_save: @delayed_save) if !@deploy.nil?
900
906
  end
901
907
  @method_semaphore.synchronize {
902
908
  @method_locks.delete(method)
@@ -126,6 +126,10 @@ module MU
126
126
  clouds = [params[:cloud]]
127
127
  params.delete(:cloud)
128
128
  end
129
+ params[:deploy_id] ||= MU.deploy_id
130
+ if !params[:deploy_id] or params[:deploy_id].empty?
131
+ raise MuError, "Can't call cleanup methods without a deploy id"
132
+ end
129
133
 
130
134
  clouds.each { |cloud|
131
135
  begin
@@ -437,7 +437,7 @@ module MU
437
437
  # @param type [String]
438
438
  # @param phase [String]
439
439
  # @param no_create_wait [Boolean]
440
- def self.addDependency(resource, name, type, phase: nil, no_create_wait: false)
440
+ def self.addDependency(resource, name, type, phase: "create", no_create_wait: false)
441
441
  if ![nil, "create", "groom"].include?(phase)
442
442
  raise MuError, "Invalid phase '#{phase}' while adding dependency #{type} #{name} to #{resource['name']}"
443
443
  end
@@ -50,6 +50,24 @@ module MU
50
50
  "default" => "index.html",
51
51
  "description" => "If +web_enabled+, return this object when \"diretory\" (a path not ending in a key/object) is invoked."
52
52
  },
53
+ "upload" => {
54
+ "type" => "array",
55
+ "items" => {
56
+ "type" => "object",
57
+ "description" => "Upload objects to a bucket, where supported",
58
+ "required" => ["source", "destination"],
59
+ "properties" => {
60
+ "source" => {
61
+ "type" => "string",
62
+ "description" => "A file or directory to upload. If a directory is specified, it will be recursively mirrored to the bucket +destination+."
63
+ },
64
+ "destination" => {
65
+ "type" => "string",
66
+ "description" => "Path to which +source+ file(s) will be uploaded in the bucket, relative to +/+"
67
+ }
68
+ }
69
+ }
70
+ },
53
71
  "policies" => {
54
72
  "type" => "array",
55
73
  "items" => MU::Config::Role.policy_primitive(subobjects: true, grant_to: true, permissions_optional: true, targets_optional: true)
@@ -59,12 +77,23 @@ module MU
59
77
  end
60
78
 
61
79
  # Generic pre-processing of {MU::Config::BasketofKittens::buckets}, bare and unvalidated.
62
- # @param _bucket [Hash]: The resource to process and validate
80
+ # @param bucket [Hash]: The resource to process and validate
63
81
  # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
64
82
  # @return [Boolean]: True if validation succeeded, False otherwise
65
- def self.validate(_bucket, _configurator)
83
+ def self.validate(bucket, _configurator)
66
84
  ok = true
67
85
 
86
+ if bucket['upload']
87
+ bucket['upload'].each { |batch|
88
+ if !File.exists?(batch['source'])
89
+ MU.log "Bucket '#{bucket['name']}' specifies upload for file/directory that does not exist", MU::ERR, details: batch
90
+ ok = false
91
+ next
92
+ end
93
+ batch['source'] = File.realpath(File.expand_path(batch['source']))
94
+ }
95
+ end
96
+
68
97
  ok
69
98
  end
70
99