cloud-mu 3.1.3 → 3.3.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +21 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +926 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +169 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/log.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/log.rb
18
18
  class Log
19
19
 
20
20
  # Base configuration schema for a Log
@@ -36,10 +36,10 @@ module MU
36
36
  end
37
37
 
38
38
  # Generic pre-processing of {MU::Config::BasketofKittens::logs}, bare and unvalidated.
39
- # @param log [Hash]: The resource to process and validate
40
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
39
+ # @param _log [Hash]: The resource to process and validate
40
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
41
41
  # @return [Boolean]: True if validation succeeded, False otherwise
42
- def self.validate(log, configurator)
42
+ def self.validate(_log, _configurator)
43
43
  ok = true
44
44
  ok
45
45
  end
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/msg_queue.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/msg_queue.rb
18
18
  class MsgQueue
19
19
 
20
20
  # Base configuration schema for a MsgQueue
@@ -34,10 +34,10 @@ module MU
34
34
  end
35
35
 
36
36
  # Generic pre-processing of {MU::Config::BasketofKittens::msg_queues}, bare and unvalidated.
37
- # @param queue [Hash]: The resource to process and validate
38
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
37
+ # @param _queue [Hash]: The resource to process and validate
38
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
39
39
  # @return [Boolean]: True if validation succeeded, False otherwise
40
- def self.validate(queue, configurator)
40
+ def self.validate(_queue, _configurator)
41
41
  ok = true
42
42
  ok
43
43
  end
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/nosqldb.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/nosqldb.rb
18
18
  class NoSQLDB
19
19
 
20
20
  # Base configuration schema for a Bucket
@@ -35,10 +35,10 @@ module MU
35
35
  end
36
36
 
37
37
  # Generic pre-processing of {MU::Config::BasketofKittens::nosqldbs}, bare and unvalidated.
38
- # @param db [Hash]: The resource to process and validate
39
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
38
+ # @param _db [Hash]: The resource to process and validate
39
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
40
40
  # @return [Boolean]: True if validation succeeded, False otherwise
41
- def self.validate(db, configurator)
41
+ def self.validate(_db, _configurator)
42
42
  ok = true
43
43
 
44
44
  ok
@@ -14,7 +14,7 @@
14
14
 
15
15
  module MU
16
16
  class Config
17
- # Basket of Kittens config schema and parser logic. See modules/mu/clouds/*/notifier.rb
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/notifier.rb
18
18
  class Notifier
19
19
 
20
20
  # Base configuration schema for a Notifier
@@ -36,12 +36,12 @@ module MU
36
36
  "items" => {
37
37
  "type" => "object",
38
38
  "description" => "A list of people or resources which should receive notifications",
39
- "required" => ["endpoint"],
40
39
  "properties" => {
41
40
  "endpoint" => {
42
41
  "type" => "string",
43
- "description" => "The endpoint which should be subscribed to this notifier, typically an email address or SMS-enabled phone number."
44
- }
42
+ "description" => "Shorthand for an endpoint which should be subscribed to this notifier, typically an email address or SMS-enabled phone number. For complex cases, such as referencing an AWS Lambda function defined elsewhere in your Mu stack, use +resource+ instead."
43
+ },
44
+ "resource" => MU::Config::Ref.schema(desc: "A cloud resource that is a valid notification target for this notifier. For simple use cases, such as external email addresses or SMS, use +endpoint+ instead.")
45
45
  }
46
46
  }
47
47
  }
@@ -51,28 +51,17 @@ module MU
51
51
 
52
52
  # Generic pre-processing of {MU::Config::BasketofKittens::notifiers}, bare and unvalidated.
53
53
  # @param notifier [Hash]: The resource to process and validate
54
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
54
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
55
55
  # @return [Boolean]: True if validation succeeded, False otherwise
56
- def self.validate(notifier, configurator)
56
+ def self.validate(notifier, _configurator)
57
57
  ok = true
58
58
 
59
+
59
60
  if notifier['subscriptions']
60
61
  notifier['subscriptions'].each { |sub|
61
- if !sub["type"]
62
- if sub["endpoint"].match(/^http:/i)
63
- sub["type"] = "http"
64
- elsif sub["endpoint"].match(/^https:/i)
65
- sub["type"] = "https"
66
- elsif sub["endpoint"].match(/^sqs:/i)
67
- sub["type"] = "sqs"
68
- elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
69
- sub["type"] = "sms"
70
- elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
71
- sub["type"] = "email"
72
- else
73
- MU.log "Notifier #{notifier['name']} subscription #{sub['endpoint']} did not specify a type, and I'm unable to guess one", MU::ERR
74
- ok = false
75
- end
62
+ if !sub['endpoint'] and !sub['resource']
63
+ MU.log "Notifier '#{notifier['name']}' must specify either resource or endpoint in subscription", MU::ERR, details: sub
64
+ ok = false
76
65
  end
77
66
  }
78
67
  end
@@ -0,0 +1,411 @@
1
+ # Copyright:: Copyright (c) 2020 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module MU
16
+
17
+ # Methods and structures for parsing Mu's configuration files. See also {MU::Config::BasketofKittens}.
18
+ class Config
19
+
20
+ # A wrapper class for resources to refer to other resources, whether they
21
+ # be a sibling object in the current deploy, an object in another deploy,
22
+ # or a plain cloud id from outside of Mu.
23
+ class Ref
24
+ attr_reader :name
25
+ attr_reader :type
26
+ attr_reader :cloud
27
+ attr_reader :deploy_id
28
+ attr_reader :region
29
+ attr_reader :credentials
30
+ attr_reader :habitat
31
+ attr_reader :mommacat
32
+ attr_reader :tag_key
33
+ attr_reader :tag_value
34
+ attr_reader :obj
35
+
36
+ @@refs = []
37
+ @@ref_semaphore = Mutex.new
38
+
39
+ # Little bit of a factory pattern... given a hash of options for a {MU::Config::Ref} objects, first see if we have an existing one that matches our more immutable attributes (+cloud+, +id+, etc). If we do, return that. If we do not, create one, add that to our inventory, and return that instead.
40
+ # @param cfg [Hash]:
41
+ # @return [MU::Config::Ref]
42
+ def self.get(cfg)
43
+ return cfg if cfg.is_a?(MU::Config::Ref)
44
+ checkfields = cfg.keys.map { |k| k.to_sym }
45
+ required = [:id, :type]
46
+
47
+ @@ref_semaphore.synchronize {
48
+ @@refs.each { |ref|
49
+ saw_mismatch = false
50
+ saw_match = false
51
+ needed_values = []
52
+ checkfields.each { |field|
53
+ next if !cfg[field]
54
+ ext_value = ref.instance_variable_get("@#{field.to_s}".to_sym)
55
+ if !ext_value
56
+ needed_values << field
57
+ next
58
+ end
59
+ if cfg[field] != ext_value
60
+ saw_mismatch = true
61
+ elsif required.include?(field) and cfg[field] == ext_value
62
+ saw_match = true
63
+ end
64
+ }
65
+ if saw_match and !saw_mismatch
66
+ # populate empty fields we got from this request
67
+ if needed_values.size > 0
68
+ newref = ref.dup
69
+ needed_values.each { |field|
70
+ newref.instance_variable_set("@#{field.to_s}".to_sym, cfg[field])
71
+ if !newref.respond_to?(field)
72
+ newref.singleton_class.instance_eval { attr_reader field.to_sym }
73
+ end
74
+ }
75
+ @@refs << newref
76
+ return newref
77
+ else
78
+ return ref
79
+ end
80
+ end
81
+ }
82
+
83
+ }
84
+
85
+ # if we get here, there was no match
86
+ newref = MU::Config::Ref.new(cfg)
87
+ @@ref_semaphore.synchronize {
88
+ @@refs << newref
89
+ return newref
90
+ }
91
+ end
92
+
93
+ # A way of dynamically defining +attr_reader+ without leaking memory
94
+ def self.define_reader(name)
95
+ define_method(name) {
96
+ instance_variable_get("@#{name.to_s}")
97
+ }
98
+ end
99
+
100
+ # @param cfg [Hash]: A Basket of Kittens configuration hash containing
101
+ # lookup information for a cloud object
102
+ def initialize(cfg)
103
+ cfg.keys.each { |field|
104
+ next if field == "tag"
105
+ if !cfg[field].nil?
106
+ self.instance_variable_set("@#{field}".to_sym, cfg[field])
107
+ elsif !cfg[field.to_sym].nil?
108
+ self.instance_variable_set("@#{field.to_s}".to_sym, cfg[field.to_sym])
109
+ end
110
+ MU::Config::Ref.define_reader(field)
111
+ }
112
+ if cfg['tag'] and cfg['tag']['key'] and
113
+ !cfg['tag']['key'].empty? and cfg['tag']['value']
114
+ @tag_key = cfg['tag']['key']
115
+ @tag_value = cfg['tag']['value']
116
+ end
117
+
118
+ if @deploy_id and !@mommacat
119
+ @mommacat = MU::MommaCat.getLitter(@deploy_id, set_context_to_me: false)
120
+ elsif @mommacat and !@deploy_id
121
+ @deploy_id = @mommacat.deploy_id
122
+ end
123
+
124
+ # canonicalize the 'type' argument
125
+ _shortclass, _cfg_name, cfg_plural, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
126
+ @type = cfg_plural if cfg_plural
127
+
128
+ kitten(shallow: true) if @mommacat # try to populate the actual cloud object for this
129
+ end
130
+
131
+ # Comparison operator
132
+ def <=>(other)
133
+ return 1 if other.nil?
134
+ self.to_s <=> other.to_s
135
+ end
136
+
137
+ # Lets callers access us like a {Hash}
138
+ # @param attribute [String,Symbol]
139
+ def [](attribute)
140
+ if respond_to?(attribute.to_sym)
141
+ send(attribute.to_sym)
142
+ else
143
+ nil
144
+ end
145
+ end
146
+
147
+ # Lets callers set attributes like a {Hash}
148
+ # @param attribute [String,Symbol]
149
+ def []=(attribute, value)
150
+ instance_variable_set("@#{attribute.to_s}".to_sym, value)
151
+ self.class.define_reader(attribute)
152
+ end
153
+
154
+ # Unset an attribute. Sort of. We can't actually do that, so nil it out
155
+ # and we get the behavior we want.
156
+ def delete(attribute)
157
+ attribute = ("@"+attribute).to_sym if attribute.to_s !~ /^@/
158
+ instance_variable_set(attribute.to_sym, nil)
159
+ end
160
+
161
+ # Base configuration schema for declared kittens referencing other cloud objects. This is essentially a set of filters that we're going to pass to {MU::MommaCat.findStray}.
162
+ # @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
163
+ # @return [Hash]
164
+ def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [], any_type: false)
165
+ parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
166
+ desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
167
+ schema = {
168
+ "type" => "object",
169
+ "#MU_REFERENCE" => true,
170
+ "minProperties" => 1,
171
+ "description" => desc,
172
+ "properties" => {
173
+ "id" => {
174
+ "type" => "string",
175
+ "description" => "Cloud identifier of a resource we want to reference, typically used when leveraging resources not managed by MU"
176
+ },
177
+ "name" => {
178
+ "type" => "string",
179
+ "description" => "The short (internal Mu) name of a resource we're attempting to reference. Typically used when referring to a sibling resource elsewhere in the same deploy, or in another known Mu deploy in conjunction with +deploy_id+."
180
+ },
181
+ "type" => {
182
+ "type" => "string",
183
+ "description" => "The resource type we're attempting to reference.",
184
+ "enum" => MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }
185
+ },
186
+ "deploy_id" => {
187
+ "type" => "string",
188
+ "description" => "Our target resource should be found in this Mu deploy."
189
+ },
190
+ "credentials" => MU::Config.credentials_primitive,
191
+ "region" => MU::Config.region_primitive,
192
+ "cloud" => MU::Config.cloud_primitive,
193
+ "tag" => {
194
+ "type" => "object",
195
+ "description" => "If the target resource supports tagging and our resource implementations +find+ method supports it, we can attempt to locate it by tag.",
196
+ "properties" => {
197
+ "key" => {
198
+ "type" => "string",
199
+ "description" => "The tag or label key to search against"
200
+ },
201
+ "value" => {
202
+ "type" => "string",
203
+ "description" => "The tag or label value to match"
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ if !["folders", "habitats"].include?(type)
210
+ schema["properties"]["habitat"] = MU::Config::Habitat.reference
211
+ end
212
+
213
+ if omit_fields
214
+ omit_fields.each { |f|
215
+ schema["properties"].delete(f)
216
+ }
217
+ end
218
+
219
+ if any_type
220
+ schema["properties"]["type"].delete("enum")
221
+ elsif !type.nil?
222
+ schema["required"] = ["type"]
223
+ schema["properties"]["type"]["default"] = type
224
+ schema["properties"]["type"]["enum"] = [type]
225
+ end
226
+
227
+ aliases.each { |a|
228
+ a.each_pair { |k, v|
229
+ if schema["properties"][v]
230
+ schema["properties"][k] = schema["properties"][v].dup
231
+ schema["properties"][k]["description"] = "Alias for <tt>#{v}</tt>"
232
+ else
233
+ MU.log "Reference schema alias #{k} wants to alias #{v}, but no such attribute exists", MU::WARN, details: caller[4]
234
+ end
235
+ }
236
+ }
237
+
238
+ schema
239
+ end
240
+
241
+ # Is our +@type+ attribute a Mu-supported type, or some rando string?
242
+ # @return [Boolean]
243
+ def is_mu_type?
244
+ _shortclass, _cfg_name, type, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
245
+ !type.nil?
246
+ end
247
+
248
+ # Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
249
+ # of the sort that would have been used to declare this reference in the
250
+ # first place.
251
+ def to_h
252
+ me = { }
253
+
254
+ self.instance_variables.each { |var|
255
+ next if [:@obj, :@mommacat, :@tag_key, :@tag_value].include?(var)
256
+ val = self.instance_variable_get(var)
257
+ next if val.nil?
258
+ val = val.to_h if val.is_a?(MU::Config::Ref)
259
+ me[var.to_s.sub(/^@/, '')] = val
260
+ }
261
+ if @tag_key and !@tag_key.empty?
262
+ me['tag'] = {
263
+ 'key' => @tag_key,
264
+ 'value' => @tag_value
265
+ }
266
+ end
267
+ me
268
+ end
269
+
270
+ # Getter for the #{id} instance variable that attempts to populate it if
271
+ # it's not set.
272
+ # @return [String,nil]
273
+ def id
274
+ return @id if @id
275
+ kitten # if it's not defined, attempt to define it
276
+ @id
277
+ end
278
+
279
+ # Alias for {id}
280
+ # @return [String,nil]
281
+ def cloud_id
282
+ id
283
+ end
284
+
285
+ # Return a {MU::Cloud} object for this reference. This is only meant to be
286
+ # called in a live deploy, which is to say that if called during initial
287
+ # configuration parsing, results may be incorrect.
288
+ # @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
289
+ def kitten(mommacat = @mommacat, shallow: false, debug: false, cloud: nil)
290
+ cloud ||= @cloud
291
+ return nil if !cloud or !@type
292
+
293
+ _shortclass, _cfg_name, cfg_plural, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
294
+ if cfg_plural
295
+ @type = cfg_plural # make sure this is the thing we expect
296
+ else
297
+ return nil # we don't do non-muish resources
298
+ end
299
+
300
+ loglevel = debug ? MU::NOTICE : MU::DEBUG
301
+
302
+ if debug
303
+ MU.log "this mf kitten", MU::WARN, details: caller
304
+ end
305
+
306
+ if @obj
307
+ @deploy_id ||= @obj.deploy_id
308
+ @id ||= @obj.cloud_id
309
+ @name ||= @obj.config['name'] if @obj.config
310
+ return @obj
311
+ end
312
+
313
+ if mommacat and !caller.grep(/`findLitterMate'/) # XXX the dumbest
314
+ MU.log "Looking for #{@type} #{@name} #{@id} in deploy #{mommacat.deploy_id}", loglevel
315
+ begin
316
+ @obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: debug)
317
+ rescue StandardError => e
318
+ if e.message =~ /deadlock/
319
+ MU.log "Saw a recursive deadlock trying to fetch kitten for Ref object in deploy #{mmommacat.deploy_id}", MU::ERR, details: to_h
320
+ end
321
+ raise e
322
+ end
323
+ if @obj # initialize missing attributes, if we can
324
+ @id ||= @obj.cloud_id
325
+ @mommacat ||= mommacat
326
+ @obj.intoDeploy(@mommacat) # make real sure these are set
327
+ @deploy_id ||= mommacat.deploy_id
328
+
329
+ if !@name
330
+ if @obj.config and @obj.config['name']
331
+ @name = @obj.config['name']
332
+ elsif @obj.mu_name
333
+ if @type == "folders"
334
+ MU.log "would assign name '#{@obj.mu_name}' in ref to this folder if I were feeling aggressive", MU::WARN, details: self.to_h
335
+ end
336
+ # @name = @obj.mu_name
337
+ end
338
+ end
339
+ return @obj
340
+ else
341
+ # MU.log "Failed to find a live '#{@type.to_s}' object named #{@name}#{@id ? " (#{@id})" : "" }#{ @habitat ? " in habitat #{@habitat}" : "" }", MU::WARN, details: self
342
+ end
343
+ end
344
+
345
+ if !@obj and !(cloud == "Google" and @id and @type == "users" and MU::Cloud.resourceClass("Google", "User").cannedServiceAcctName?(@id)) and !shallow
346
+ try_deploy_id = @deploy_id
347
+
348
+ begin
349
+ hab_arg = if @habitat.nil?
350
+ [nil]
351
+ elsif @habitat.is_a?(MU::Config::Ref)
352
+ [@habitat.id]
353
+ elsif @habitat.is_a?(Hash)
354
+ [@habitat["id"]]
355
+ else
356
+ [@habitat.to_s]
357
+ end
358
+
359
+ MU.log "Ref#kitten calling findStray", loglevel, details: {
360
+ cloud: cloud,
361
+ type: @type,
362
+ name: @name,
363
+ cloud_id: @id,
364
+ deploy_id: try_deploy_id,
365
+ region: @region,
366
+ habitats: hab_arg,
367
+ credentials: @credentials,
368
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
369
+ }
370
+
371
+ found = MU::MommaCat.findStray(
372
+ cloud,
373
+ @type,
374
+ name: @name,
375
+ cloud_id: @id,
376
+ deploy_id: try_deploy_id,
377
+ region: @region,
378
+ habitats: hab_arg,
379
+ credentials: @credentials,
380
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
381
+ )
382
+ MU.log "Ref#kitten results from findStray", loglevel, details: found
383
+ @obj ||= found.first if found
384
+ rescue MU::MommaCat::MultipleMatches => e
385
+ if try_deploy_id.nil? and MU.deploy_id
386
+ MU.log "Attempting to narrow down #{cloud} #{@type} to #{MU.deploy_id}", MU::NOTICE
387
+ try_deploy_id = MU.deploy_id
388
+ retry
389
+ else
390
+ raise e
391
+ end
392
+ rescue ThreadError => e
393
+ # Sometimes MommaCat calls us in a potential deadlock situation;
394
+ # don't be the cause of a fatal error if so, we don't need this
395
+ # object that badly.
396
+ raise e if !e.message.match(/recursive locking/)
397
+ end
398
+ end
399
+
400
+ if @obj
401
+ @deploy_id ||= @obj.deploy_id
402
+ @id ||= @obj.cloud_id
403
+ @name ||= @obj.config['name']
404
+ end
405
+
406
+ @obj
407
+ end
408
+
409
+ end
410
+ end
411
+ end