cloud-mu 3.1.6 → 3.4.0

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +15 -12
  4. data/bin/mu-azure-tests +57 -0
  5. data/bin/mu-cleanup +2 -4
  6. data/bin/mu-configure +37 -1
  7. data/bin/mu-deploy +3 -3
  8. data/bin/mu-findstray-tests +25 -0
  9. data/bin/mu-gen-docs +2 -4
  10. data/bin/mu-load-config.rb +2 -1
  11. data/bin/mu-run-tests +37 -12
  12. data/cloud-mu.gemspec +4 -4
  13. data/cookbooks/mu-tools/attributes/default.rb +7 -0
  14. data/cookbooks/mu-tools/libraries/helper.rb +87 -3
  15. data/cookbooks/mu-tools/recipes/apply_security.rb +39 -23
  16. data/cookbooks/mu-tools/recipes/aws_api.rb +13 -0
  17. data/cookbooks/mu-tools/recipes/google_api.rb +4 -0
  18. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  19. data/cookbooks/mu-tools/resources/disk.rb +33 -12
  20. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  21. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  22. data/extras/clean-stock-amis +10 -2
  23. data/extras/generate-stock-images +7 -3
  24. data/extras/image-generators/AWS/centos7.yaml +19 -16
  25. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  26. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  27. data/modules/mommacat.ru +2 -2
  28. data/modules/mu.rb +84 -97
  29. data/modules/mu/adoption.rb +359 -59
  30. data/modules/mu/cleanup.rb +67 -44
  31. data/modules/mu/cloud.rb +108 -1754
  32. data/modules/mu/cloud/database.rb +49 -0
  33. data/modules/mu/cloud/dnszone.rb +44 -0
  34. data/modules/mu/cloud/machine_images.rb +212 -0
  35. data/modules/mu/cloud/providers.rb +81 -0
  36. data/modules/mu/cloud/resource_base.rb +929 -0
  37. data/modules/mu/cloud/server.rb +40 -0
  38. data/modules/mu/cloud/server_pool.rb +1 -0
  39. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  40. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  41. data/modules/mu/cloud/wrappers.rb +178 -0
  42. data/modules/mu/config.rb +122 -80
  43. data/modules/mu/config/alarm.rb +2 -6
  44. data/modules/mu/config/bucket.rb +32 -3
  45. data/modules/mu/config/cache_cluster.rb +2 -2
  46. data/modules/mu/config/cdn.rb +100 -0
  47. data/modules/mu/config/collection.rb +1 -1
  48. data/modules/mu/config/container_cluster.rb +2 -2
  49. data/modules/mu/config/database.rb +84 -105
  50. data/modules/mu/config/database.yml +1 -2
  51. data/modules/mu/config/dnszone.rb +5 -4
  52. data/modules/mu/config/doc_helpers.rb +4 -5
  53. data/modules/mu/config/endpoint.rb +2 -1
  54. data/modules/mu/config/firewall_rule.rb +3 -19
  55. data/modules/mu/config/folder.rb +1 -1
  56. data/modules/mu/config/function.rb +17 -8
  57. data/modules/mu/config/group.rb +1 -1
  58. data/modules/mu/config/habitat.rb +1 -1
  59. data/modules/mu/config/job.rb +89 -0
  60. data/modules/mu/config/loadbalancer.rb +57 -11
  61. data/modules/mu/config/log.rb +1 -1
  62. data/modules/mu/config/msg_queue.rb +1 -1
  63. data/modules/mu/config/nosqldb.rb +1 -1
  64. data/modules/mu/config/notifier.rb +8 -19
  65. data/modules/mu/config/ref.rb +81 -9
  66. data/modules/mu/config/role.rb +1 -1
  67. data/modules/mu/config/schema_helpers.rb +30 -34
  68. data/modules/mu/config/search_domain.rb +1 -1
  69. data/modules/mu/config/server.rb +5 -13
  70. data/modules/mu/config/server_pool.rb +3 -7
  71. data/modules/mu/config/storage_pool.rb +1 -1
  72. data/modules/mu/config/tail.rb +10 -0
  73. data/modules/mu/config/user.rb +1 -1
  74. data/modules/mu/config/vpc.rb +13 -17
  75. data/modules/mu/defaults/AWS.yaml +106 -106
  76. data/modules/mu/defaults/Azure.yaml +1 -0
  77. data/modules/mu/defaults/Google.yaml +1 -0
  78. data/modules/mu/deploy.rb +33 -19
  79. data/modules/mu/groomer.rb +15 -0
  80. data/modules/mu/groomers/chef.rb +3 -0
  81. data/modules/mu/logger.rb +120 -144
  82. data/modules/mu/master.rb +22 -1
  83. data/modules/mu/mommacat.rb +71 -26
  84. data/modules/mu/mommacat/daemon.rb +23 -14
  85. data/modules/mu/mommacat/naming.rb +82 -3
  86. data/modules/mu/mommacat/search.rb +59 -16
  87. data/modules/mu/mommacat/storage.rb +119 -48
  88. data/modules/mu/{clouds → providers}/README.md +1 -1
  89. data/modules/mu/{clouds → providers}/aws.rb +248 -62
  90. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  91. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  92. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  93. data/modules/mu/providers/aws/cdn.rb +782 -0
  94. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  95. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +65 -63
  96. data/modules/mu/providers/aws/database.rb +1747 -0
  97. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  98. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  99. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  100. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  101. data/modules/mu/{clouds → providers}/aws/function.rb +291 -133
  102. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  103. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  104. data/modules/mu/providers/aws/job.rb +469 -0
  105. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  106. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  107. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  108. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  109. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  110. data/modules/mu/{clouds → providers}/aws/role.rb +112 -78
  111. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  112. data/modules/mu/{clouds → providers}/aws/server.rb +120 -145
  113. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  114. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  115. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  116. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  117. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  118. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  119. data/modules/mu/{clouds → providers}/aws/vpc.rb +141 -73
  120. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  121. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  122. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  123. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  124. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  126. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  127. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  128. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  129. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  130. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  132. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  133. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  134. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  135. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  136. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  137. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  138. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  142. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  143. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  144. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  145. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  146. data/modules/mu/{clouds → providers}/google.rb +15 -6
  147. data/modules/mu/{clouds → providers}/google/bucket.rb +2 -2
  148. data/modules/mu/{clouds → providers}/google/container_cluster.rb +29 -14
  149. data/modules/mu/{clouds → providers}/google/database.rb +2 -9
  150. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +3 -3
  151. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  152. data/modules/mu/{clouds → providers}/google/function.rb +4 -4
  153. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  154. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  155. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +2 -2
  156. data/modules/mu/{clouds → providers}/google/role.rb +46 -35
  157. data/modules/mu/{clouds → providers}/google/server.rb +26 -11
  158. data/modules/mu/{clouds → providers}/google/server_pool.rb +11 -11
  159. data/modules/mu/{clouds → providers}/google/user.rb +32 -22
  160. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  161. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  162. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  163. data/modules/mu/{clouds → providers}/google/vpc.rb +38 -3
  164. data/modules/tests/aws-jobs-functions.yaml +46 -0
  165. data/modules/tests/centos6.yaml +15 -0
  166. data/modules/tests/centos7.yaml +15 -0
  167. data/modules/tests/centos8.yaml +12 -0
  168. data/modules/tests/ecs.yaml +2 -2
  169. data/modules/tests/eks.yaml +1 -1
  170. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  171. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  172. data/modules/tests/microservice_app.yaml +288 -0
  173. data/modules/tests/rds.yaml +108 -0
  174. data/modules/tests/regrooms/rds.yaml +123 -0
  175. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  176. data/modules/tests/super_complex_bok.yml +2 -2
  177. data/modules/tests/super_simple_bok.yml +2 -2
  178. data/spec/mu/clouds/azure_spec.rb +2 -2
  179. metadata +126 -98
  180. data/modules/mu/clouds/aws/database.rb +0 -1974
  181. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -23,7 +23,7 @@ $opts = Optimist::options do
23
23
  #{$0} [-c credentials] [-i imagename]
24
24
  EOS
25
25
  opt :credentials, "Use these AWS credentials from mu.yaml instead of the default set", :required => false, :type => :string
26
- opt :image, "Purge a specific image, instead of just scrubing old ones", :required => false, :type => :string
26
+ opt :image, "Purge a specific image, instead of just scrubbing old ones", :required => false, :type => :string
27
27
  end
28
28
 
29
29
  filters = [
@@ -33,12 +33,14 @@ filters = [
33
33
  }
34
34
  ]
35
35
 
36
+ in_use = MU::Cloud.getStockImage("AWS").values.flatten.map { |h| h.values }.flatten
36
37
 
37
38
  MU::Cloud::AWS.listRegions.each { | r|
38
39
  images = MU::Cloud::AWS.ec2(region: r, credentials: $opts[:credentials]).describe_images(
39
40
  filters: filters + [{ "name" => "state", "values" => ["available"]}]
40
41
  ).images
41
42
  images.each { |ami|
43
+ next if in_use.include?(ami)
42
44
  if ($opts[:image] and ami.name == $opts[:image]) or
43
45
  ((DateTime.now.to_time - DateTime.parse(ami.creation_date).to_time) > 15552000 and ami.name.match(/^MU-(PROD|DEV)/))
44
46
  snaps = []
@@ -53,7 +55,13 @@ MU::Cloud::AWS.listRegions.each { | r|
53
55
  rescue Aws::EC2::Errors::InvalidAMIIDUnavailable
54
56
  end
55
57
  snaps.each { |snap_id|
56
- MU::Cloud::AWS.ec2(region: r, credentials: $opts[:credentials]).delete_snapshot(snapshot_id: snap_id)
58
+ begin
59
+ MU::Cloud::AWS.ec2(region: r, credentials: $opts[:credentials]).delete_snapshot(snapshot_id: snap_id)
60
+ rescue Aws::EC2::Errors::InvalidSnapshotInUse
61
+ sleep 5
62
+ retry
63
+ rescue Aws::EC2::Errors::InvalidSnapshotNotFound
64
+ end
57
65
  }
58
66
  end
59
67
  }
@@ -63,6 +63,7 @@ end
63
63
  now = DateTime.now
64
64
 
65
65
  exitcode = 0
66
+ succeeded = 0
66
67
  $opts[:clouds].each { |cloud|
67
68
  current_images = MU::Cloud.getStockImage(cloud, fail_hard: true)
68
69
  $opts[:platforms].each { |platform|
@@ -91,6 +92,7 @@ $opts[:clouds].each { |cloud|
91
92
  end
92
93
  next if !needed
93
94
  end
95
+ MU.log "Loading "+bok_dir+"/"+cloud+"/"+platform+".yaml"
94
96
  conf_engine = MU::Config.new(
95
97
  bok_dir+"/"+cloud+"/"+platform+".yaml",
96
98
  default_credentials: $opts[(cloud.downcase+"_creds").to_sym]
@@ -113,6 +115,7 @@ $opts[:clouds].each { |cloud|
113
115
  # Scrub any loose metadata left over from our image deployment. It's
114
116
  # ok, this won't touch the images we just made.
115
117
  MU::Cleanup.run(deployer.mommacat.deploy_id, skipsnapshots: true, verbosity: MU::Logger::QUIET)
118
+ succeeded += 1
116
119
  rescue Exception => e
117
120
  MU.log e.message, MU::ERR
118
121
  exitcode = 1
@@ -121,10 +124,11 @@ $opts[:clouds].each { |cloud|
121
124
  end
122
125
  }
123
126
 
124
- if !available_clouds.keys.include?("AWS") # XXX or if we don't have permissions
127
+ if !$opts[:dryrun] and succeeded > 0
125
128
  puts current_images.to_yaml
126
- elsif !$opts[:dryrun]
127
- MU::Cloud::AWS::Bucket.upload($opts[:upload_to]+"/"+cloud+".yaml", data: current_images.to_yaml, credentials: $opts[:aws_creds], acl: "public-read")
129
+ if available_clouds.keys.include?("AWS")
130
+ MU::Cloud::AWS::Bucket.upload($opts[:upload_to]+"/"+cloud+".yaml", data: current_images.to_yaml, credentials: $opts[:aws_creds], acl: "public-read")
131
+ end
128
132
  end
129
133
  }
130
134
 
@@ -1,17 +1,20 @@
1
1
  ---
2
- appname: mu
3
- servers:
4
- -
5
- name: centos7
6
- platform: centos7
7
- size: m3.medium
8
- scrub_groomer: true
9
- run_list:
10
- - recipe[mu-tools::apply_security]
11
- - recipe[mu-tools::updates]
12
- - recipe[mu-tools::split_var_partitions]
13
- create_image:
14
- image_then_destroy: true
15
- public: true
16
- copy_to_regions:
17
- - "#ALL"
2
+ appname: mu
3
+ servers:
4
+ - name: centos7
5
+ platform: centos7
6
+ size: m4.large
7
+ vpc:
8
+ name: c7vpc
9
+ scrub_groomer: true
10
+ run_list:
11
+ - recipe[mu-tools::apply_security]
12
+ - recipe[mu-tools::updates]
13
+ - recipe[mu-tools::split_var_partitions]
14
+ create_image:
15
+ image_then_destroy: true
16
+ public: true
17
+ copy_to_regions:
18
+ - "#ALL"
19
+ vpcs:
20
+ - name: c7vpc
@@ -387,7 +387,7 @@ app = proc do |env|
387
387
 
388
388
  # XXX We can't assume AWS anymore. What does this look like otherwise?
389
389
  # If this is an already-groomed instance, try to get a real object for it
390
- instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: false, calling_deploy: kittenpile).first
390
+ instance = MU::MommaCat.findStray("AWS", "server", cloud_id: req["mu_instance_id"], region: server_cfg["region"], deploy_id: req["mu_id"], name: req["mu_resource_name"], dummy_ok: true, calling_deploy: kittenpile).first
391
391
  mu_name = nil
392
392
  if instance.nil?
393
393
  # Now we're just checking for existence in the cloud provider, really
@@ -416,7 +416,7 @@ app = proc do |env|
416
416
  if instance.respond_to?(:addVolume)
417
417
  # XXX make sure we handle mangled input safely
418
418
  params = JSON.parse(Base64.decode64(req["add_volume"]))
419
- MU.log "ADDVOLUME REQUEST", MU::WARN, details: params
419
+ MU.log "add_volume request", MU::NOTICE, details: params
420
420
  instance.addVolume(params["dev"], params["size"], delete_on_termination: params["delete_on_termination"])
421
421
  else
422
422
  returnval = throw500 "I don't know how to add a volume for #{instance}"
@@ -79,38 +79,40 @@ class Hash
79
79
  }
80
80
  return 0 if self == other # that was easy!
81
81
  # compare elements and decide who's "bigger" based on their totals?
82
- 0
82
+
83
+ # fine, try some brute force and just hope everything implements to_s
84
+ self.flatten.map { |e| e.to_s }.join() <=> other.flatten.map { |e| e.to_s }.join()
83
85
  end
84
86
 
85
- # Recursively compare two hashes
86
- def diff(with, on = self, level: 0, parents: [])
87
+ # Recursively compare two Mu Basket of Kittens hashes and report the differences
88
+ def diff(with, on = self, level: 0, parents: [], report: {}, habitat: nil)
87
89
  return if with.nil? and on.nil?
88
90
  if with.nil? or on.nil? or with.class != on.class
89
91
  return # XXX ...however we're flagging differences
90
92
  end
91
93
  return if on == with
92
94
 
93
- tree = ""
94
- indentsize = 0
95
- parents.each { |p|
96
- tree += (" " * indentsize) + p + " => \n"
97
- indentsize += 2
98
- }
99
- indent = (" " * indentsize)
100
-
101
95
  changes = []
96
+ report ||= {}
102
97
  if on.is_a?(Hash)
103
98
  on_unique = (on.keys - with.keys)
104
99
  with_unique = (with.keys - on.keys)
105
100
  shared = (with.keys & on.keys)
106
101
  shared.each { |k|
107
- diff(with[k], on[k], level: level+1, parents: parents + [k])
102
+
103
+ report_data = diff(with[k], on[k], level: level+1, parents: parents + [k], report: report[k], habitat: habitat)
104
+ if report_data and !report_data.empty?
105
+ report ||= {}
106
+ report[k] = report_data
107
+ end
108
108
  }
109
109
  on_unique.each { |k|
110
- changes << "- ".red+PP.pp({k => on[k] }, '')
110
+ report[k] = { :action => :removed, :parents => parents, :value => on[k].clone }
111
+ report[k][:habitat] = habitat if habitat
111
112
  }
112
113
  with_unique.each { |k|
113
- changes << "+ ".green+PP.pp({k => with[k]}, '')
114
+ report[k] = { :action => :added, :parents => parents, :value => with[k].clone }
115
+ report[k][:habitat] = habitat if habitat
114
116
  }
115
117
  elsif on.is_a?(Array)
116
118
  return if with == on
@@ -122,29 +124,27 @@ class Hash
122
124
  # sorting arrays full of weird, non-primitive types.
123
125
  done = []
124
126
  on.sort.each { |elt|
125
- if elt.is_a?(Hash) and elt['name'] or elt['entity']# or elt['cloud_id']
126
- with.sort.each { |other_elt|
127
- # Figure out what convention this thing is using for resource identification
128
- compare_a, compare_b = if elt['name'].nil? and elt["id"].nil? and !elt["entity"].nil? and !other_elt["entity"].nil?
129
- [elt["entity"], other_elt["entity"]]
130
- else
131
- [elt, other_elt]
132
- end
127
+ if elt.is_a?(Hash) and !MU::MommaCat.getChunkName(elt).first.nil?
128
+ elt_namestr, elt_location, elt_location_list = MU::MommaCat.getChunkName(elt)
133
129
 
134
- if (compare_a['name'] and compare_b['name'] == compare_a['name']) or
135
- (compare_a['name'].nil? and !compare_a["id"].nil? and compare_a["id"] == compare_b["id"])
136
- break if elt == other_elt
130
+ with.sort.each { |other_elt|
131
+ other_elt_namestr, other_elt_location, other_elt_location_list = MU::MommaCat.getChunkName(other_elt)
132
+
133
+ # Case 1: The array element exists in both version of this array
134
+ if elt_namestr and other_elt_namestr and
135
+ elt_namestr == other_elt_namestr and
136
+ (elt_location.nil? or other_elt_location.nil? or
137
+ elt_location == other_elt_location or
138
+ !(elt_location_list & other_elt_location_list).empty?
139
+ )
137
140
  done << elt
138
141
  done << other_elt
139
- namestr = if elt['type']
140
- "#{elt['type']}[#{elt['name']}]"
141
- elsif elt['name']
142
- elt['name']
143
- elsif elt['entity'] and elt["entity"]["id"]
144
- elt['entity']['id']
142
+ break if elt == other_elt # if they're identical, we're done
143
+ report_data = diff(other_elt, elt, level: level+1, parents: parents + [elt_namestr], habitat: (elt_location || habitat))
144
+ if report_data and !report_data.empty?
145
+ report ||= {}
146
+ report[elt_namestr] = report_data
145
147
  end
146
-
147
- diff(other_elt, elt, level: level+1, parents: parents + [namestr])
148
148
  break
149
149
  end
150
150
  }
@@ -152,43 +152,34 @@ class Hash
152
152
  }
153
153
  on_unique = (on - with) - done
154
154
  with_unique = (with - on) - done
155
- # if on_unique.size > 0 or with_unique.size > 0
156
- # if before_a != after_a
157
- # MU.log "A BEFORE", MU::NOTICE, details: before_a
158
- # MU.log "A AFTER", MU::NOTICE, details: after_a
159
- # end
160
- # if before_b != after_b
161
- # MU.log "B BEFORE", MU::NOTICE, details: before_b
162
- # MU.log "B AFTER", MU::NOTICE, details: after_b
163
- # end
164
- # end
155
+
156
+ # Case 2: This array entry exists in the old version, but not the new one
165
157
  on_unique.each { |e|
166
- changes << if e.is_a?(Hash)
167
- "- ".red+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
168
- else
169
- "- ".red+e.to_s
170
- end
158
+ namestr, loc = MU::MommaCat.getChunkName(e)
159
+
160
+ report ||= {}
161
+ report[namestr] = { :action => :removed, :parents => parents, :value => e.clone }
162
+ report[namestr][:habitat] = loc if loc
171
163
  }
164
+
165
+ # Case 3: This array entry exists in the new version, but not the old one
172
166
  with_unique.each { |e|
173
- changes << if e.is_a?(Hash)
174
- "+ ".green+PP.pp(Hash.bok_minimize(e), '').gsub(/\n/, "\n "+(indent))
175
- else
176
- "+ ".green+e.to_s
177
- end
167
+ namestr, loc = MU::MommaCat.getChunkName(e)
168
+
169
+ report ||= {}
170
+ report[namestr] = { :action => :added, :parents => parents, :value => e.clone }
171
+ report[namestr][:habitat] = loc if loc
178
172
  }
173
+
174
+ # A plain old leaf node of data
179
175
  else
180
176
  if on != with
181
- changes << "-".red+" #{on.to_s}"
182
- changes << "+".green+" #{with.to_s}"
177
+ report = { :action => :changed, :parents => parents, :oldvalue => on, :value => with.clone }
178
+ report[:habitat] = habitat if habitat
183
179
  end
184
180
  end
185
181
 
186
- if changes.size > 0
187
- puts tree
188
- changes.each { |c|
189
- puts indent+c
190
- }
191
- end
182
+ report.freeze
192
183
  end
193
184
 
194
185
  # Implement a merge! that just updates each hash leaf as needed, not
@@ -212,8 +203,29 @@ class Hash
212
203
  end
213
204
 
214
205
  ENV['HOME'] = Etc.getpwuid(Process.uid).dir
206
+ module MU
207
+
208
+ # For log entries that should only be logged when we're in verbose mode
209
+ DEBUG = 0.freeze
210
+ # For ordinary log entries
211
+ INFO = 1.freeze
212
+ # For more interesting log entries which are not errors
213
+ NOTICE = 2.freeze
214
+ # Log entries for non-fatal errors
215
+ WARN = 3.freeze
216
+ # Log entries for non-fatal errors
217
+ WARNING = 3.freeze
218
+ # Log entries for fatal errors
219
+ ERR = 4.freeze
220
+ # Log entries for fatal errors
221
+ ERROR = 4.freeze
222
+ # Log entries that will be held and displayed/emailed at the end of deploy,
223
+ # cleanup, etc.
224
+ SUMMARY = 5.freeze
225
+ end
215
226
 
216
227
  require 'mu/logger'
228
+
217
229
  module MU
218
230
 
219
231
  # Subclass core thread so we can gracefully handle it when we hit system
@@ -273,8 +285,9 @@ module MU
273
285
  # Wrapper class for fatal Exceptions. Gives our internals something to
274
286
  # inherit that will log an error message appropriately before bubbling up.
275
287
  class MuError < StandardError
276
- def initialize(message = nil, silent: false)
277
- MU.log message, MU::ERR, details: caller[2] if !message.nil? and !silent
288
+ def initialize(message = nil, silent: false, details: nil)
289
+ details ||= caller[2]
290
+ MU.log message, MU::ERR, details: details if !message.nil? and !silent
278
291
  if MU.verbosity == MU::Logger::SILENT
279
292
  super ""
280
293
  else
@@ -286,8 +299,8 @@ module MU
286
299
  # Wrapper class for temporary Exceptions. Gives our internals something to
287
300
  # inherit that will log a notice message appropriately before bubbling up.
288
301
  class MuNonFatal < StandardError
289
- def initialize(message = nil, silent: false)
290
- 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
291
304
  if MU.verbosity == MU::Logger::SILENT
292
305
  super ""
293
306
  else
@@ -620,25 +633,6 @@ module MU
620
633
  @@logger.log(msg, level, details: details, html: html, verbosity: verbosity, color: color)
621
634
  end
622
635
 
623
- # For log entries that should only be logged when we're in verbose mode
624
- DEBUG = 0.freeze
625
- # For ordinary log entries
626
- INFO = 1.freeze
627
- # For more interesting log entries which are not errors
628
- NOTICE = 2.freeze
629
- # Log entries for non-fatal errors
630
- WARN = 3.freeze
631
- # Log entries for non-fatal errors
632
- WARNING = 3.freeze
633
- # Log entries for fatal errors
634
- ERR = 4.freeze
635
- # Log entries for fatal errors
636
- ERROR = 4.freeze
637
- # Log entries that will be held and displayed/emailed at the end of deploy,
638
- # cleanup, etc.
639
- SUMMARY = 5.freeze
640
-
641
-
642
636
  autoload :Cleanup, 'mu/cleanup'
643
637
  autoload :Deploy, 'mu/deploy'
644
638
  autoload :MommaCat, 'mu/mommacat'
@@ -652,7 +646,7 @@ module MU
652
646
  new_cfg = $MU_CFG.dup
653
647
  examples = {}
654
648
  MU::Cloud.supportedClouds.each { |cloud|
655
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
649
+ cloudclass = MU::Cloud.cloudClass(cloud)
656
650
  begin
657
651
  if cloudclass.hosted? and !$MU_CFG[cloud.downcase]
658
652
  cfg_blob = cloudclass.hosted_config
@@ -808,11 +802,7 @@ module MU
808
802
  # @param groomer [String]: The grooming agent to load.
809
803
  # @return [Class]: The class object implementing this groomer agent
810
804
  def self.loadGroomer(groomer)
811
- if !File.size?(MU.myRoot+"/modules/mu/groomers/#{groomer.downcase}.rb")
812
- raise MuError, "Requested to use unsupported grooming agent #{groomer}"
813
- end
814
- require "mu/groomers/#{groomer.downcase}"
815
- return Object.const_get("MU").const_get("Groomer").const_get(groomer)
805
+ MU::Groomer.loadGroomer(groomer)
816
806
  end
817
807
 
818
808
  @@myRegion_var = nil
@@ -966,8 +956,7 @@ module MU
966
956
 
967
957
  @@myCloudDescriptor = nil
968
958
  if MU.myCloud
969
- svrclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud).const_get("Server")
970
- found = svrclass.find(cloud_id: @@myInstanceId, region: MU.myRegion) # XXX need habitat arg for google et al
959
+ found = MU::Cloud.resourceClass(MU.myCloud, "Server").find(cloud_id: @@myInstanceId, region: MU.myRegion) # XXX need habitat arg for google et al
971
960
  # found = MU::MommaCat.findStray(MU.myCloud, "server", cloud_id: @@myInstanceId, dummy_ok: true, region: MU.myRegion)
972
961
  if !found.nil? and found.size == 1
973
962
  @@myCloudDescriptor = found.values.first
@@ -980,8 +969,7 @@ module MU
980
969
  def self.myVPCObj
981
970
  return nil if MU.myCloud.nil?
982
971
  return @@myVPCObj_var if @@myVPCObj_var
983
- cloudclass = const_get("MU").const_get("Cloud").const_get(MU.myCloud)
984
- @@myVPCObj_var ||= cloudclass.myVPCObj
972
+ @@myVPCObj_var ||= MU::Cloud.cloudClass(MU.myCloud).myVPCObj
985
973
  @@myVPCObj_var
986
974
  end
987
975
 
@@ -1106,10 +1094,9 @@ module MU
1106
1094
 
1107
1095
  clouds = platform.nil? ? MU::Cloud.supportedClouds : [platform]
1108
1096
  clouds.each { |cloud|
1109
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
1110
- bucketname = cloudclass.adminBucketName(credentials)
1097
+ bucketname = MU::Cloud.cloudClass(cloud).adminBucketName(credentials)
1111
1098
  begin
1112
- if platform or (cloudclass.hosted? and platform.nil?) or cloud == MU::Config.defaultCloud
1099
+ if platform or (MU::Cloud.cloudClass(cloud).hosted? and platform.nil?) or cloud == MU::Config.defaultCloud
1113
1100
  return bucketname
1114
1101
  end
1115
1102
  end
@@ -30,7 +30,7 @@ module MU
30
30
  :omnibus => "Jam everything into one monolothic configuration"
31
31
  }
32
32
 
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)
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)
34
34
  @scraped = {}
35
35
  @clouds = clouds
36
36
  @types = types
@@ -44,8 +44,11 @@ module MU
44
44
  @savedeploys = savedeploys
45
45
  @diff = diff
46
46
  @habitats = habitats
47
+ @regions = regions
47
48
  @habitats ||= []
48
49
  @scrub_mu_isms = scrub_mu_isms
50
+ @merge = merge
51
+ @pattern = pattern
49
52
  end
50
53
 
51
54
  # Walk cloud providers with available credentials to discover resources
@@ -53,7 +56,7 @@ module MU
53
56
  @default_parent = nil
54
57
 
55
58
  @clouds.each { |cloud|
56
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
59
+ cloudclass = MU::Cloud.cloudClass(cloud)
57
60
  next if cloudclass.listCredentials.nil?
58
61
 
59
62
  if cloud == "Google" and !@parent and @target_creds
@@ -65,7 +68,6 @@ module MU
65
68
 
66
69
  cloudclass.listCredentials.each { |credset|
67
70
  next if @sources and !@sources.include?(credset)
68
-
69
71
  cfg = cloudclass.credConfig(credset)
70
72
  if cfg and cfg['restrict_to_habitats']
71
73
  cfg['restrict_to_habitats'] << cfg['project'] if cfg['project']
@@ -90,7 +92,7 @@ module MU
90
92
 
91
93
  @types.each { |type|
92
94
  begin
93
- resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
95
+ resclass = MU::Cloud.resourceClass(cloud, type)
94
96
  rescue ::MU::Cloud::MuCloudResourceNotImplemented
95
97
  next
96
98
  end
@@ -106,6 +108,7 @@ module MU
106
108
  credentials: credset,
107
109
  allow_multi: true,
108
110
  habitats: @habitats.dup,
111
+ region: @regions,
109
112
  dummy_ok: true,
110
113
  skip_provider_owned: true,
111
114
  # debug: false#,
@@ -114,7 +117,9 @@ module MU
114
117
 
115
118
  if found and found.size > 0
116
119
  if resclass.cfg_plural == "habitats"
117
- found.reject! { |h| !cloudclass.listHabitats(credset).include?(h) }
120
+ found.reject! { |h|
121
+ !cloudclass.listHabitats(credset).include?(h.cloud_id)
122
+ }
118
123
  end
119
124
  MU.log "Found #{found.size.to_s} raw #{resclass.cfg_plural} in #{cloud}"
120
125
  @scraped[type] ||= {}
@@ -122,7 +127,12 @@ module MU
122
127
  if obj.habitat and !cloudclass.listHabitats(credset).include?(obj.habitat)
123
128
  next
124
129
  end
130
+
125
131
  # XXX apply any filters (e.g. MU-ID tags)
132
+ if obj.cloud_id.nil?
133
+ MU.log "This damn thing gave me no cloud id, what do I even do with that", MU::ERR, details: obj
134
+ exit
135
+ end
126
136
  @scraped[type][obj.cloud_id] = obj
127
137
  }
128
138
  end
@@ -200,7 +210,36 @@ module MU
200
210
  prefix = "mu" if prefix.empty? # so that appnames aren't ever empty
201
211
  end
202
212
 
213
+ # Find any previous deploys with this particular profile, which we'll use
214
+ # later for --diff.
215
+ @existing_deploys = {}
216
+ @existing_deploys_by_id = {}
217
+ @origins = {}
218
+ @types_found_in = {}
219
+ groupings.each_pair { |appname, types|
220
+ allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
221
+ next if (types & allowed_types).size == 0
222
+ origin = {
223
+ "appname" => prefix+appname,
224
+ "types" => (types & allowed_types).sort,
225
+ "habitats" => @habitats.sort,
226
+ "group_by" => @group_by.to_s
227
+ }
228
+
229
+ @existing_deploys[appname] = MU::MommaCat.findMatchingDeploy(origin)
230
+ if @existing_deploys[appname]
231
+ @existing_deploys_by_id[@existing_deploys[appname].deploy_id] = @existing_deploys[appname]
232
+ @origins[appname] = origin
233
+ origin['types'].each { |t|
234
+ @types_found_in[t] = @existing_deploys[appname]
235
+ }
236
+ end
237
+ }
238
+
203
239
  groupings.each_pair { |appname, types|
240
+ allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
241
+ next if (types & allowed_types).size == 0
242
+
204
243
  bok = { "appname" => prefix+appname }
205
244
  if @scrub_mu_isms
206
245
  bok["scrub_mu_isms"] = true
@@ -210,26 +249,23 @@ module MU
210
249
  end
211
250
 
212
251
  count = 0
213
- allowed_types = @types.map { |t| MU::Cloud.resource_types[t][:cfg_plural] }
214
- next if (types & allowed_types).size == 0
215
- origin = {
216
- "appname" => bok['appname'],
217
- "types" => (types & allowed_types).sort,
218
- "habitats" => @habitats.sort,
219
- "group_by" => @group_by.to_s
220
- }
221
-
222
- deploy = MU::MommaCat.findMatchingDeploy(origin)
223
- if @diff and !deploy
224
- MU.log "--diff was set but I failed to find a deploy like me to compare to", MU::ERR, details: origin
225
- exit 1
252
+ if @diff
253
+ if !@existing_deploys[appname]
254
+ MU.log "--diff was set but I failed to find a deploy like '#{appname}' to compare to (have #{@existing_deploys.keys.join(", ")})", MU::ERR, details: @origins[appname]
255
+ exit 1
256
+ else
257
+ MU.log "Will diff current live resources against #{@existing_deploys[appname].deploy_id}", MU::NOTICE, details: @origins[appname]
258
+ end
226
259
  end
227
260
 
228
261
  threads = []
262
+ timers = {}
263
+ walltimers = {}
229
264
  @clouds.each { |cloud|
230
265
  @scraped.each_pair { |type, resources|
266
+ typestart = Time.now
231
267
  res_class = begin
232
- MU::Cloud.loadCloudType(cloud, type)
268
+ MU::Cloud.resourceClass(cloud, type)
233
269
  rescue MU::Cloud::MuCloudResourceNotImplemented
234
270
  # XXX I don't think this can actually happen
235
271
  next
@@ -237,6 +273,7 @@ module MU
237
273
  next if !types.include?(res_class.cfg_plural)
238
274
 
239
275
  bok[res_class.cfg_plural] ||= []
276
+ timers[type] ||= {}
240
277
 
241
278
  class_semaphore = Mutex.new
242
279
 
@@ -253,13 +290,18 @@ module MU
253
290
  end
254
291
  end
255
292
  threads << Thread.new(obj_thr) { |obj|
293
+ start = Time.now
256
294
 
257
- kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats)
258
- if kitten_cfg
295
+ kitten_cfg = obj.toKitten(rootparent: @default_parent, billing: @billing, habitats: @habitats, types: @types)
296
+ if kitten_cfg and (!@pattern or @pattern.match(kitten_cfg['name']))
259
297
  print "."
260
298
  kitten_cfg.delete("credentials") if @target_creds
261
299
  class_semaphore.synchronize {
262
300
  bok[res_class.cfg_plural] << kitten_cfg
301
+ if !kitten_cfg['cloud_id']
302
+ MU.log "No cloud id in this #{res_class.cfg_name} kitten!", MU::ERR, details: kitten_cfg
303
+ end
304
+ timers[type][kitten_cfg['cloud_id']] = (Time.now - start)
263
305
  }
264
306
  count += 1
265
307
  end
@@ -270,6 +312,7 @@ module MU
270
312
  threads.each { |t|
271
313
  t.join
272
314
  }
315
+
273
316
  puts ""
274
317
  bok[res_class.cfg_plural].sort! { |a, b|
275
318
  strs = [a, b].map { |x|
@@ -291,24 +334,30 @@ module MU
291
334
  bok[res_class.cfg_plural].each { |sibling|
292
335
  next if kitten_cfg == sibling
293
336
  if sibling['name'] == kitten_cfg['name']
294
- MU.log "#{res_class.cfg_name} name #{sibling['name']} unavailable, will attempt to rename duplicate object", MU::DEBUG, details: kitten_cfg
295
- if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
296
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['parent'].id
297
- elsif kitten_cfg['project']
298
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['project']
299
- elsif kitten_cfg['region']
300
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['region']
301
- elsif kitten_cfg['cloud_id']
302
- kitten_cfg['name'] = kitten_cfg['name']+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
303
- else
304
- raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{sibling['name']} and couldn't come up with a good way to differentiate them"
305
- end
337
+ MU::Adoption.deDuplicateName(kitten_cfg, res_class)
306
338
  MU.log "De-duplication: Renamed #{res_class.cfg_name} name '#{sibling['name']}' => '#{kitten_cfg['name']}'", MU::NOTICE
307
339
  break
308
340
  end
309
341
  }
310
342
  }
343
+ walltimers[type] ||= 0
344
+ walltimers[type] += (Time.now - typestart)
345
+ }
346
+ }
347
+
348
+ timers.each_pair { |type, resources|
349
+ next if resources.empty?
350
+ total = resources.values.sum
351
+ top_5 = resources.keys.sort { |a, b|
352
+ resources[b] <=> resources[a]
353
+ }.slice(0, 5).map { |k|
354
+ k.to_s+": "+sprintf("%.2fs", resources[k])
311
355
  }
356
+ if walltimers[type] < 45
357
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])}"
358
+ else
359
+ MU.log "Kittened #{resources.size.to_s} eligible #{type}s in #{sprintf("%.2fs", walltimers[type])} (CPU time #{sprintf("%.2fs", total)}, avg #{sprintf("%.2fs", total/resources.size)}). Top 5:", MU::NOTICE, details: top_5
360
+ end
312
361
  }
313
362
 
314
363
  # No matching resources isn't necessarily an error
@@ -317,23 +366,36 @@ module MU
317
366
  # Now walk through all of the Refs in these objects, resolve them, and minimize
318
367
  # their config footprint
319
368
  MU.log "Minimizing footprint of #{count.to_s} found resources", MU::DEBUG
320
- @boks[bok['appname']] = vacuum(bok, origin: origin, save: @savedeploys)
321
369
 
322
- if @diff and !deploy
370
+ generated_deploy = generateStubDeploy(bok)
371
+ @boks[bok['appname']] = vacuum(bok, origin: @origins[appname], deploy: generated_deploy, save: @savedeploys)
372
+
373
+ if @diff and !@existing_deploys[appname]
323
374
  MU.log "diff flag set, but no comparable deploy provided for #{bok['appname']}", MU::ERR
324
375
  exit 1
325
376
  end
326
377
 
327
- if deploy and @diff
328
- prevcfg = MU::Config.manxify(vacuum(deploy.original_config, deploy: deploy))
378
+ if @diff
379
+ prev_vacuumed = vacuum(@existing_deploys[appname].original_config, deploy: @existing_deploys[appname], keep_missing: true, copy_from: generated_deploy)
380
+ prevcfg = MU::Config.manxify(prev_vacuumed)
329
381
  if !prevcfg
330
- MU.log "#{deploy.deploy_id} didn't have a working original config for me to compare", MU::ERR
382
+ MU.log "#{@existing_deploys[appname].deploy_id} didn't have a working original config for me to compare", MU::ERR
331
383
  exit 1
332
384
  end
333
385
  newcfg = MU::Config.manxify(@boks[bok['appname']])
386
+ report = prevcfg.diff(newcfg)
387
+
388
+ if report
389
+
390
+ if MU.muCfg['adopt_change_notify']
391
+ notifyChanges(@existing_deploys[appname], report.freeze)
392
+ end
393
+ if @merge
394
+ MU.log "Saving changes to #{@existing_deploys[appname].deploy_id}"
395
+ @existing_deploys[appname].updateBasketofKittens(newcfg, save_now: true)
396
+ end
397
+ end
334
398
 
335
- prevcfg.diff(newcfg)
336
- exit
337
399
  end
338
400
  }
339
401
  @boks
@@ -341,6 +403,183 @@ module MU
341
403
 
342
404
  private
343
405
 
406
+ # @param tier [Hash]
407
+ # @param parent_key [String]
408
+ def crawlChangeReport(tier, parent_key = nil, indent: "")
409
+ report = []
410
+ if tier.is_a?(Array)
411
+ tier.each { |a|
412
+ sub_report = crawlChangeReport(a, parent_key)
413
+ report.concat(sub_report) if sub_report and !sub_report.empty?
414
+ }
415
+ elsif tier.is_a?(Hash)
416
+ if tier[:action]
417
+ preposition = if tier[:action] == :added
418
+ "to"
419
+ elsif tier[:action] == :removed
420
+ "from"
421
+ else
422
+ "in"
423
+ end
424
+
425
+ name = ""
426
+ type_of = parent_key.sub(/s$|\[.*/, '') if parent_key
427
+ loc = tier[:habitat]
428
+
429
+ if tier[:value] and tier[:value].is_a?(Hash)
430
+ name, loc = MU::MommaCat.getChunkName(tier[:value], type_of)
431
+ elsif parent_key
432
+ name = parent_key
433
+ end
434
+
435
+ path_str = []
436
+ slack_path_str = ""
437
+ if tier[:parents] and tier[:parents].size > 2
438
+ path = tier[:parents].clone
439
+ slack_path_str += "#{preposition} \*"+path.join(" ⇨ ")+"\*" if path.size > 0
440
+ path.shift
441
+ path.shift
442
+ path.pop if path.last == name
443
+ for c in (0..(path.size-1)) do
444
+ path_str << (" " * (c+2)) + (path[c] || "<nil>")
445
+ end
446
+ end
447
+ path_str << "" if !path_str.empty?
448
+
449
+ plain = (name ? name : type_of) if name or type_of
450
+ plain ||= "" # XXX but this is a problem
451
+ slack = "`"+plain+"`"
452
+
453
+ plain += " ("+loc+")" if loc and !loc.empty?
454
+ color = plain
455
+
456
+ if tier[:action] == :added
457
+ color = "+ ".green + plain
458
+ plain = "+ " + plain
459
+ slack += " added"
460
+ elsif tier[:action] == :removed
461
+ color = "- ".red + plain
462
+ plain = "- " + plain
463
+ slack += " removed"
464
+ end
465
+
466
+ slack += " #{tier[:action]} #{preposition} \*#{loc}\*" if loc and !loc.empty? and [Array, Hash].include?(tier[:value].class)
467
+
468
+ plain = path_str.join(" => \n") + indent + plain
469
+ color = path_str.join(" => \n") + indent + color
470
+
471
+ slack += " "+slack_path_str if !slack_path_str.empty?
472
+ myreport = {
473
+ "slack" => slack,
474
+ "plain" => plain,
475
+ "color" => color
476
+ }
477
+
478
+ append = ""
479
+ if tier[:value] and (tier[:value].is_a?(Array) or tier[:value].is_a?(Hash))
480
+ if tier[:value].is_a?(Hash)
481
+ if name
482
+ tier[:value].delete("entity")
483
+ tier[:value].delete(name.sub(/\[.*/, '')) if name
484
+ end
485
+ if (tier[:value].keys - ["id", "name", "type"]).size > 0
486
+ myreport["details"] = tier[:value].clone
487
+ append = PP.pp(tier[:value], '').gsub(/(^|\n)/, '\1'+indent)
488
+ end
489
+ else
490
+ append = indent+"["+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s.light_blue }.join(", ")+"]"
491
+ slack += " #{tier[:action].to_s}: "+tier[:value].map { |v| MU::MommaCat.getChunkName(v, type_of).reverse.join("/") || v.to_s }.join(", ")
492
+ end
493
+ else
494
+ tier[:value] ||= "<nil>"
495
+ if ![:removed].include?(tier[:action])
496
+ myreport["slack"] += ". New #{tier[:field] ? "`"+tier[:field]+"`" : :value}: \*#{tier[:value]}\*"
497
+ else
498
+ myreport["slack"] += " (was \*#{tier[:value]}\*)"
499
+ end
500
+ append = tier[:value].to_s.bold
501
+ end
502
+
503
+ if append and !append.empty?
504
+ myreport["plain"] += " =>\n "+indent+append
505
+ myreport["color"] += " =>\n "+indent+append
506
+ end
507
+
508
+ report << myreport if tier[:action]
509
+ end
510
+
511
+ # Just because we've got changes at this level doesn't mean there aren't
512
+ # more further down.
513
+ tier.each_pair { |k, v|
514
+ next if !(v.is_a?(Hash) or v.is_a?(Array))
515
+ sub_report = crawlChangeReport(v, k, indent: indent+" ")
516
+ report.concat(sub_report) if sub_report and !sub_report.empty?
517
+ }
518
+ end
519
+
520
+ report
521
+ end
522
+
523
+
524
+ def notifyChanges(deploy, report)
525
+ snippet_threshold = (MU.muCfg['adopt_change_notify'] && MU.muCfg['adopt_change_notify']['slack_snippet_threshold']) || 5
526
+
527
+ report.each_pair { |res_type, resources|
528
+ shortclass, _cfg_name, _cfg_plural, _classname = MU::Cloud.getResourceNames(res_type, false)
529
+ next if !shortclass # we don't really care about Mu metadata changes
530
+ resources.each_pair { |name, data|
531
+ if MU::MommaCat.getChunkName(data[:value], res_type).first.nil?
532
+ symbol = if data[:action] == :added
533
+ "+".green
534
+ elsif data[:action] == :removed
535
+ "-".red
536
+ else
537
+ "~".yellow
538
+ end
539
+ puts (symbol+" "+res_type+"["+name+"]")
540
+ end
541
+
542
+ noun = shortclass ? shortclass.to_s : res_type.capitalize
543
+ verb = if data[:action]
544
+ data[:action].to_s
545
+ else
546
+ "modified"
547
+ end
548
+
549
+ changes = crawlChangeReport(data.freeze, res_type)
550
+
551
+ slacktext = "#{noun} \*#{name}\* was #{verb}"
552
+ if data[:habitat]
553
+ slacktext += " in \*#{data[:habitat]}\*"
554
+ end
555
+ snippets = []
556
+
557
+ if [:added, :removed].include?(data[:action]) and data[:value]
558
+ snippets << { text: "```"+JSON.pretty_generate(data[:value])+"```" }
559
+ else
560
+ changes.each { |c|
561
+ slacktext += "\n • "+c["slack"]
562
+ if c["details"]
563
+ details = JSON.pretty_generate(c["details"])
564
+ snippets << { text: "```"+JSON.pretty_generate(c["details"])+"```" }
565
+ end
566
+ }
567
+ end
568
+
569
+ changes.each { |c|
570
+ puts c["color"]
571
+ }
572
+ puts ""
573
+
574
+ if MU.muCfg['adopt_change_notify'] and MU.muCfg['adopt_change_notify']['slack']
575
+ deploy.sendAdminSlack(slacktext, scrub_mu_isms: MU.muCfg['adopt_scrub_mu_isms'], snippets: snippets, noop: false)
576
+ end
577
+
578
+ }
579
+ }
580
+
581
+ end
582
+
344
583
  def scrubSchemaDefaults(conf_chunk, schema_chunk, depth = 0, type: nil)
345
584
  return if schema_chunk.nil?
346
585
 
@@ -372,8 +611,7 @@ module MU
372
611
  # theory
373
612
  realschema = if type and schema_chunk["items"] and schema_chunk["items"]["properties"] and item["cloud"] and MU::Cloud.supportedClouds.include?(item['cloud'])
374
613
 
375
- cloudclass = Object.const_get("MU").const_get("Cloud").const_get(item["cloud"]).const_get(type)
376
- _toplevel_required, cloudschema = cloudclass.schema(self)
614
+ _toplevel_required, cloudschema = MU::Cloud.resourceClass(item['cloud'], type).schema(self)
377
615
 
378
616
  newschema = schema_chunk["items"].dup
379
617
  newschema["properties"].merge!(cloudschema)
@@ -397,8 +635,7 @@ module MU
397
635
  # Do the same for our main objects: if they all use the same credentials,
398
636
  # for example, remove the explicit +credentials+ attributes and set that
399
637
  # value globally, once.
400
- def vacuum(bok, origin: nil, save: false, deploy: nil)
401
- deploy ||= generateStubDeploy(bok)
638
+ def vacuum(bok, origin: nil, save: false, deploy: nil, copy_from: nil, keep_missing: false)
402
639
 
403
640
  globals = {
404
641
  'cloud' => {},
@@ -418,11 +655,24 @@ module MU
418
655
  end
419
656
  }
420
657
  obj = deploy.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
658
+ inject_metadata = save
659
+ if obj.nil? and copy_from
660
+ obj = copy_from.findLitterMate(type: attrs[:cfg_plural], name: resource['name'])
661
+ if obj
662
+ inject_metadata = true
663
+ obj.intoDeploy(deploy, force: true)
664
+ end
665
+ end
666
+
421
667
  begin
422
668
  raise Incomplete if obj.nil?
669
+ if inject_metadata
670
+ deploydata = obj.notify
671
+ deploy.notify(attrs[:cfg_plural], resource['name'], deploydata, triggering_node: obj)
672
+ end
423
673
  new_cfg = resolveReferences(resource, deploy, obj)
424
674
  new_cfg.delete("cloud_id")
425
- cred_cfg = MU::Cloud.const_get(obj.cloud).credConfig(obj.credentials)
675
+ cred_cfg = MU::Cloud.cloudClass(obj.cloud).credConfig(obj.credentials)
426
676
  if cred_cfg['region'] == new_cfg['region']
427
677
  new_cfg.delete('region')
428
678
  end
@@ -432,6 +682,11 @@ module MU
432
682
  end
433
683
  processed << new_cfg
434
684
  rescue Incomplete
685
+ if keep_missing
686
+ processed << resource
687
+ else
688
+ MU.log "#{attrs[:cfg_name]} #{resource['name']} didn't show up from findLitterMate", MU::WARN, details: deploy.original_config[attrs[:cfg_plural]].reject { |r| r['name'] != "" }
689
+ end
435
690
  end
436
691
  }
437
692
 
@@ -442,24 +697,23 @@ module MU
442
697
 
443
698
  # Pare out global values like +cloud+ or +region+ that appear to be
444
699
  # universal in the deploy we're creating.
445
- def scrub_globals(h, field)
700
+ scrub_globals = Proc.new { |h, field|
446
701
  if h.is_a?(Hash)
447
702
  newhash = {}
448
703
  h.each_pair { |k, v|
449
704
  next if k == field
450
- newhash[k] = scrub_globals(v, field)
705
+ newhash[k] = scrub_globals.call(v, field)
451
706
  }
452
707
  h = newhash
453
708
  elsif h.is_a?(Array)
454
709
  newarr = []
455
710
  h.each { |v|
456
- newarr << scrub_globals(v, field)
711
+ newarr << scrub_globals.call(v, field)
457
712
  }
458
- h = newarr
713
+ h = newarr.uniq
459
714
  end
460
-
461
715
  h
462
- end
716
+ }
463
717
 
464
718
  globals.each_pair { |field, counts|
465
719
  next if counts.size != 1
@@ -469,7 +723,7 @@ module MU
469
723
  if bok[attrs[:cfg_plural]]
470
724
  new_resources = []
471
725
  bok[attrs[:cfg_plural]].each { |resource|
472
- new_resources << scrub_globals(resource, field)
726
+ new_resources << scrub_globals.call(resource, field)
473
727
  }
474
728
  bok[attrs[:cfg_plural]] = new_resources
475
729
  end
@@ -487,11 +741,33 @@ module MU
487
741
  end
488
742
 
489
743
  def resolveReferences(cfg, deploy, parent)
744
+ mask_deploy_id = false
745
+
746
+ check_deploy_id = Proc.new { |cfgblob|
747
+ (deploy and
748
+ (cfgblob.is_a?(MU::Config::Ref) or cfgblob.is_a?(Hash)) and
749
+ cfgblob['deploy_id'] and
750
+ cfgblob['deploy_id'] != deploy.deploy_id and
751
+ @diff and
752
+ @types_found_in[cfgblob['type']] and
753
+ @types_found_in[cfgblob['type']].deploy_id == cfgblob['deploy_id']
754
+ )
755
+ }
756
+
757
+ mask_deploy_id = check_deploy_id.call(cfg)
758
+
490
759
  if cfg.is_a?(MU::Config::Ref)
491
- cfg.kitten(deploy) || cfg.kitten
760
+ if mask_deploy_id
761
+ cfg.delete("deploy_id")
762
+ cfg.delete("mommacat")
763
+ cfg.kitten(deploy)
764
+ else
765
+ cfg.kitten(deploy) || cfg.kitten
766
+ end
767
+
492
768
  hashcfg = cfg.to_h
493
769
 
494
- if cfg.kitten(deploy)
770
+ if cfg.kitten
495
771
  littermate = deploy.findLitterMate(type: cfg.type, name: cfg.name, cloud_id: cfg.id, habitat: cfg.habitat)
496
772
 
497
773
  if littermate and littermate.config['name']
@@ -516,13 +792,12 @@ module MU
516
792
  elsif hashcfg["id"] and !hashcfg["name"]
517
793
  hashcfg.delete("deploy_id")
518
794
  else
519
- pp parent.cloud_desc
520
- raise Incomplete, "Failed to resolve reference on behalf of #{parent}"
795
+ raise Incomplete.new "Failed to resolve reference on behalf of #{parent}", details: hashcfg
521
796
  end
522
797
  hashcfg.delete("deploy_id") if hashcfg['deploy_id'] == deploy.deploy_id
523
798
 
524
799
  if parent and parent.config
525
- cred_cfg = MU::Cloud.const_get(parent.cloud).credConfig(parent.credentials)
800
+ cred_cfg = MU::Cloud.cloudClass(parent.cloud).credConfig(parent.credentials)
526
801
 
527
802
  if parent.config['region'] == hashcfg['region'] or
528
803
  cred_cfg['region'] == hashcfg['region']
@@ -581,7 +856,12 @@ module MU
581
856
  MU.log "Dropping unresolved value", MU::WARN, details: value
582
857
  end
583
858
  }
584
- cfg = new_array
859
+ cfg = new_array.uniq
860
+ end
861
+
862
+ if mask_deploy_id or check_deploy_id.call(cfg)
863
+ cfg.delete("deploy_id")
864
+ MU.log "#{parent} in #{deploy.deploy_id} references something in #{@types_found_in[cfg['type']].deploy_id}, ditching extraneous deploy_id", MU::DEBUG, details: cfg.to_h
585
865
  end
586
866
 
587
867
  cfg
@@ -631,6 +911,10 @@ module MU
631
911
 
632
912
  if !@scraped[typename][kitten['cloud_id']]
633
913
  MU.log "No object in scraped tree for #{attrs[:cfg_name]} #{kitten['cloud_id']} (#{kitten['name']})", MU::ERR, details: kitten
914
+ if kitten['cloud_id'].nil?
915
+ pp caller
916
+ exit
917
+ end
634
918
  next
635
919
  end
636
920
 
@@ -641,7 +925,8 @@ module MU
641
925
  deploy.addKitten(
642
926
  attrs[:cfg_plural],
643
927
  kitten['name'],
644
- @scraped[typename][kitten['cloud_id']]
928
+ @scraped[typename][kitten['cloud_id']],
929
+ do_notify: true
645
930
  )
646
931
  }
647
932
  end
@@ -650,6 +935,21 @@ module MU
650
935
  deploy
651
936
  end
652
937
 
938
+ def self.deDuplicateName(kitten_cfg, res_class)
939
+ orig_name = kitten_cfg['name'].dup
940
+ if kitten_cfg['parent'] and kitten_cfg['parent'].respond_to?(:id) and kitten_cfg['parent'].id
941
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['parent'].id
942
+ elsif kitten_cfg['project']
943
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['project']
944
+ elsif kitten_cfg['region']
945
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['region']
946
+ elsif kitten_cfg['cloud_id']
947
+ kitten_cfg['name'] = kitten_cfg['name']+"-"+kitten_cfg['cloud_id'].gsub(/[^a-z0-9]/i, "-")
948
+ else
949
+ raise MU::Config::DuplicateNameError, "Saw duplicate #{res_class.cfg_name} name #{orig_name} and couldn't come up with a good way to differentiate them"
950
+ end
951
+ end
952
+
653
953
  # Go through everything we've scraped and update our mappings of cloud ids
654
954
  # and bare name fields, so that resources can reference one another
655
955
  # portably by name.