cloud-mu 3.1.6 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.