cloud-mu 3.1.3 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +10 -2
  3. data/bin/mu-adopt +5 -1
  4. data/bin/mu-load-config.rb +2 -3
  5. data/bin/mu-run-tests +112 -27
  6. data/cloud-mu.gemspec +20 -20
  7. data/cookbooks/mu-tools/libraries/helper.rb +2 -1
  8. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  9. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  10. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  11. data/extras/image-generators/Google/centos6.yaml +1 -0
  12. data/extras/image-generators/Google/centos7.yaml +1 -1
  13. data/modules/mommacat.ru +5 -15
  14. data/modules/mu.rb +10 -14
  15. data/modules/mu/adoption.rb +20 -14
  16. data/modules/mu/cleanup.rb +13 -9
  17. data/modules/mu/cloud.rb +26 -26
  18. data/modules/mu/clouds/aws.rb +100 -59
  19. data/modules/mu/clouds/aws/alarm.rb +4 -2
  20. data/modules/mu/clouds/aws/bucket.rb +25 -21
  21. data/modules/mu/clouds/aws/cache_cluster.rb +25 -23
  22. data/modules/mu/clouds/aws/collection.rb +21 -20
  23. data/modules/mu/clouds/aws/container_cluster.rb +47 -26
  24. data/modules/mu/clouds/aws/database.rb +57 -68
  25. data/modules/mu/clouds/aws/dnszone.rb +14 -14
  26. data/modules/mu/clouds/aws/endpoint.rb +20 -16
  27. data/modules/mu/clouds/aws/firewall_rule.rb +19 -16
  28. data/modules/mu/clouds/aws/folder.rb +7 -7
  29. data/modules/mu/clouds/aws/function.rb +15 -12
  30. data/modules/mu/clouds/aws/group.rb +14 -10
  31. data/modules/mu/clouds/aws/habitat.rb +16 -13
  32. data/modules/mu/clouds/aws/loadbalancer.rb +16 -15
  33. data/modules/mu/clouds/aws/log.rb +13 -10
  34. data/modules/mu/clouds/aws/msg_queue.rb +15 -8
  35. data/modules/mu/clouds/aws/nosqldb.rb +18 -11
  36. data/modules/mu/clouds/aws/notifier.rb +11 -6
  37. data/modules/mu/clouds/aws/role.rb +87 -70
  38. data/modules/mu/clouds/aws/search_domain.rb +30 -19
  39. data/modules/mu/clouds/aws/server.rb +102 -72
  40. data/modules/mu/clouds/aws/server_pool.rb +47 -28
  41. data/modules/mu/clouds/aws/storage_pool.rb +5 -6
  42. data/modules/mu/clouds/aws/user.rb +13 -10
  43. data/modules/mu/clouds/aws/vpc.rb +135 -121
  44. data/modules/mu/clouds/azure.rb +16 -9
  45. data/modules/mu/clouds/azure/container_cluster.rb +2 -3
  46. data/modules/mu/clouds/azure/firewall_rule.rb +10 -10
  47. data/modules/mu/clouds/azure/habitat.rb +8 -6
  48. data/modules/mu/clouds/azure/loadbalancer.rb +5 -5
  49. data/modules/mu/clouds/azure/role.rb +8 -10
  50. data/modules/mu/clouds/azure/server.rb +65 -25
  51. data/modules/mu/clouds/azure/user.rb +5 -7
  52. data/modules/mu/clouds/azure/vpc.rb +12 -15
  53. data/modules/mu/clouds/cloudformation.rb +8 -7
  54. data/modules/mu/clouds/cloudformation/vpc.rb +2 -4
  55. data/modules/mu/clouds/google.rb +39 -24
  56. data/modules/mu/clouds/google/bucket.rb +9 -11
  57. data/modules/mu/clouds/google/container_cluster.rb +27 -42
  58. data/modules/mu/clouds/google/database.rb +6 -9
  59. data/modules/mu/clouds/google/firewall_rule.rb +11 -10
  60. data/modules/mu/clouds/google/folder.rb +16 -9
  61. data/modules/mu/clouds/google/function.rb +127 -161
  62. data/modules/mu/clouds/google/group.rb +21 -18
  63. data/modules/mu/clouds/google/habitat.rb +18 -15
  64. data/modules/mu/clouds/google/loadbalancer.rb +14 -16
  65. data/modules/mu/clouds/google/role.rb +48 -31
  66. data/modules/mu/clouds/google/server.rb +105 -105
  67. data/modules/mu/clouds/google/server_pool.rb +12 -31
  68. data/modules/mu/clouds/google/user.rb +67 -13
  69. data/modules/mu/clouds/google/vpc.rb +58 -65
  70. data/modules/mu/config.rb +89 -1738
  71. data/modules/mu/config/bucket.rb +3 -3
  72. data/modules/mu/config/collection.rb +3 -3
  73. data/modules/mu/config/container_cluster.rb +2 -2
  74. data/modules/mu/config/dnszone.rb +5 -5
  75. data/modules/mu/config/doc_helpers.rb +517 -0
  76. data/modules/mu/config/endpoint.rb +3 -3
  77. data/modules/mu/config/firewall_rule.rb +118 -3
  78. data/modules/mu/config/folder.rb +3 -3
  79. data/modules/mu/config/function.rb +2 -2
  80. data/modules/mu/config/group.rb +3 -3
  81. data/modules/mu/config/habitat.rb +3 -3
  82. data/modules/mu/config/loadbalancer.rb +3 -3
  83. data/modules/mu/config/log.rb +3 -3
  84. data/modules/mu/config/msg_queue.rb +3 -3
  85. data/modules/mu/config/nosqldb.rb +3 -3
  86. data/modules/mu/config/notifier.rb +2 -2
  87. data/modules/mu/config/ref.rb +333 -0
  88. data/modules/mu/config/role.rb +3 -3
  89. data/modules/mu/config/schema_helpers.rb +508 -0
  90. data/modules/mu/config/search_domain.rb +3 -3
  91. data/modules/mu/config/server.rb +86 -58
  92. data/modules/mu/config/server_pool.rb +2 -2
  93. data/modules/mu/config/tail.rb +189 -0
  94. data/modules/mu/config/user.rb +3 -3
  95. data/modules/mu/config/vpc.rb +44 -4
  96. data/modules/mu/defaults/Google.yaml +2 -2
  97. data/modules/mu/deploy.rb +13 -10
  98. data/modules/mu/groomer.rb +1 -1
  99. data/modules/mu/groomers/ansible.rb +69 -24
  100. data/modules/mu/groomers/chef.rb +52 -44
  101. data/modules/mu/logger.rb +17 -14
  102. data/modules/mu/master.rb +317 -2
  103. data/modules/mu/master/chef.rb +3 -4
  104. data/modules/mu/master/ldap.rb +3 -3
  105. data/modules/mu/master/ssl.rb +12 -2
  106. data/modules/mu/mommacat.rb +85 -1766
  107. data/modules/mu/mommacat/daemon.rb +394 -0
  108. data/modules/mu/mommacat/naming.rb +366 -0
  109. data/modules/mu/mommacat/storage.rb +689 -0
  110. data/modules/tests/bucket.yml +4 -0
  111. data/modules/tests/{win2k12.yaml → needwork/win2k12.yaml} +0 -0
  112. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  113. data/modules/tests/regrooms/bucket.yml +19 -0
  114. metadata +112 -102
@@ -57,10 +57,10 @@ module MU
57
57
  end
58
58
 
59
59
  # Generic pre-processing of {MU::Config::BasketofKittens::endpoints}, bare and unvalidated.
60
- # @param endpoint [Hash]: The resource to process and validate
61
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
60
+ # @param _endpoint [Hash]: The resource to process and validate
61
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
62
62
  # @return [Boolean]: True if validation succeeded, False otherwise
63
- def self.validate(endpoint, configurator)
63
+ def self.validate(_endpoint, _configurator)
64
64
  ok = true
65
65
 
66
66
  ok
@@ -100,14 +100,129 @@ module MU
100
100
  end
101
101
 
102
102
  # Generic pre-processing of {MU::Config::BasketofKittens::firewall_rules}, bare and unvalidated.
103
- # @param acl [Hash]: The resource to process and validate
104
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
103
+ # @param _acl [Hash]: The resource to process and validate
104
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
105
105
  # @return [Boolean]: True if validation succeeded, False otherwise
106
- def self.validate(acl, configurator)
106
+ def self.validate(_acl, _configurator)
107
107
  ok = true
108
108
  ok
109
109
  end
110
110
 
111
111
  end
112
+
113
+ # FirewallRules can reference other FirewallRules, which means we need to do
114
+ # an extra pass to make sure we get all intra-stack dependencies correct.
115
+ # @param acl [Hash]: The configuration hash for the FirewallRule to check
116
+ # @return [Hash]
117
+ def resolveIntraStackFirewallRefs(acl, delay_validation = false)
118
+ acl["rules"].each { |acl_include|
119
+ if acl_include['sgs']
120
+ acl_include['sgs'].each { |sg_ref|
121
+ if haveLitterMate?(sg_ref, "firewall_rules")
122
+ acl["dependencies"] ||= []
123
+ found = false
124
+ acl["dependencies"].each { |dep|
125
+ if dep["type"] == "firewall_rule" and dep["name"] == sg_ref
126
+ dep["no_create_wait"] = true
127
+ found = true
128
+ end
129
+ }
130
+ if !found
131
+ acl["dependencies"] << {
132
+ "type" => "firewall_rule",
133
+ "name" => sg_ref,
134
+ "no_create_wait" => true
135
+ }
136
+ end
137
+ siblingfw = haveLitterMate?(sg_ref, "firewall_rules")
138
+ if !siblingfw["#MU_VALIDATED"]
139
+ # XXX raise failure somehow
140
+ insertKitten(siblingfw, "firewall_rules", delay_validation: delay_validation)
141
+ end
142
+ end
143
+ }
144
+ end
145
+ }
146
+ acl
147
+ end
148
+
149
+ # Generate configuration for the general-purpose admin firewall rulesets
150
+ # (security groups in AWS). Note that these are unique to regions and
151
+ # individual VPCs (as well as Classic, which is just a degenerate case of
152
+ # a VPC for our purposes.
153
+ # @param vpc [Hash]: A VPC reference as defined in our config schema. This originates with the calling resource, so we'll peel out just what we need (a name or cloud id of a VPC).
154
+ # @param admin_ip [String]: Optional string of an extra IP address to allow blanket access to the calling resource.
155
+ # @param cloud [String]: The parent resource's cloud plugin identifier
156
+ # @param region [String]: Cloud provider region, if applicable.
157
+ # @return [Hash<String>]: A dependency description that the calling resource can then add to itself.
158
+ def adminFirewallRuleset(vpc: nil, admin_ip: nil, region: nil, cloud: nil, credentials: nil, rules_only: false)
159
+ if !cloud or (cloud == "AWS" and !region)
160
+ raise MuError, "Cannot call adminFirewallRuleset without specifying the parent's region and cloud provider"
161
+ end
162
+ hosts = Array.new
163
+ hosts << "#{MU.my_public_ip}/32" if MU.my_public_ip
164
+ hosts << "#{MU.my_private_ip}/32" if MU.my_private_ip
165
+ hosts << "#{MU.mu_public_ip}/32" if MU.mu_public_ip
166
+ hosts << "#{admin_ip}/32" if admin_ip
167
+ hosts.uniq!
168
+
169
+ rules = []
170
+ if cloud == "Google"
171
+ rules = [
172
+ { "ingress" => true, "proto" => "all", "hosts" => hosts },
173
+ { "egress" => true, "proto" => "all", "hosts" => hosts }
174
+ ]
175
+ else
176
+ rules = [
177
+ { "proto" => "tcp", "port_range" => "0-65535", "hosts" => hosts },
178
+ { "proto" => "udp", "port_range" => "0-65535", "hosts" => hosts },
179
+ { "proto" => "icmp", "port_range" => "-1", "hosts" => hosts }
180
+ ]
181
+ end
182
+
183
+ resclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get("FirewallRule")
184
+
185
+ if rules_only
186
+ return rules
187
+ end
188
+
189
+ name = "admin"
190
+ name += credentials.to_s if credentials
191
+ realvpc = nil
192
+ if vpc
193
+ realvpc = {}
194
+ ['vpc_name', 'vpc_id'].each { |p|
195
+ if vpc[p]
196
+ vpc[p.sub(/^vpc_/, '')] = vpc[p]
197
+ vpc.delete(p)
198
+ end
199
+ }
200
+ ['cloud', 'id', 'name', 'deploy_id', 'habitat', 'credentials'].each { |field|
201
+ realvpc[field] = vpc[field] if !vpc[field].nil?
202
+ }
203
+ if !realvpc['id'].nil? and !realvpc['id'].empty?
204
+ # Stupid kludge for Google cloud_ids which are sometimes URLs and
205
+ # sometimes not. Requirements are inconsistent from scenario to
206
+ # scenario.
207
+ name = name + "-" + realvpc['id'].gsub(/.*\//, "")
208
+ realvpc['id'] = getTail("id", value: realvpc['id'], prettyname: "Admin Firewall Ruleset #{name} Target VPC", cloudtype: "AWS::EC2::VPC::Id") if realvpc["id"].is_a?(String)
209
+ elsif !realvpc['name'].nil?
210
+ name = name + "-" + realvpc['name']
211
+ end
212
+ end
213
+
214
+
215
+ acl = {"name" => name, "rules" => rules, "vpc" => realvpc, "cloud" => cloud, "admin" => true, "credentials" => credentials }
216
+ if cloud == "Google" and acl["vpc"] and acl["vpc"]["habitat"]
217
+ acl['project'] = acl["vpc"]["habitat"]["id"] || acl["vpc"]["habitat"]["name"]
218
+ end
219
+ acl.delete("vpc") if !acl["vpc"]
220
+ if !resclass.isGlobal? and !region.nil? and !region.empty?
221
+ acl["region"] = region
222
+ end
223
+ @admin_firewall_rules << acl if !@admin_firewall_rules.include?(acl)
224
+ return {"type" => "firewall_rule", "name" => name}
225
+ end
226
+
112
227
  end
113
228
  end
@@ -59,10 +59,10 @@ module MU
59
59
  end
60
60
 
61
61
  # Generic pre-processing of {MU::Config::BasketofKittens::folder}, bare and unvalidated.
62
- # @param folder [Hash]: The resource to process and validate
63
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
62
+ # @param _folder [Hash]: The resource to process and validate
63
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
64
64
  # @return [Boolean]: True if validation succeeded, False otherwise
65
- def self.validate(folder, configurator)
65
+ def self.validate(_folder, _configurator)
66
66
  ok = true
67
67
  ok
68
68
  end
@@ -99,9 +99,9 @@ module MU
99
99
 
100
100
  # Generic pre-processing of {MU::Config::BasketofKittens::functions}, bare and unvalidated.
101
101
  # @param function [Hash]: The resource to process and validate
102
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
102
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
103
103
  # @return [Boolean]: True if validation succeeded, False otherwise
104
- def self.validate(function, configurator)
104
+ def self.validate(function, _configurator)
105
105
  ok = true
106
106
  if !function['code']
107
107
  ok = false
@@ -51,10 +51,10 @@ module MU
51
51
  end
52
52
 
53
53
  # Generic pre-processing of {MU::Config::BasketofKittens::group}, bare and unvalidated.
54
- # @param group [Hash]: The resource to process and validate
55
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
54
+ # @param _group [Hash]: The resource to process and validate
55
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
56
56
  # @return [Boolean]: True if validation succeeded, False otherwise
57
- def self.validate(group, configurator)
57
+ def self.validate(_group, _configurator)
58
58
  ok = true
59
59
  ok
60
60
  end
@@ -38,10 +38,10 @@ module MU
38
38
  end
39
39
 
40
40
  # Generic pre-processing of {MU::Config::BasketofKittens::habitat}, bare and unvalidated.
41
- # @param habitat [Hash]: The resource to process and validate
42
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
41
+ # @param _habitat [Hash]: The resource to process and validate
42
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
43
43
  # @return [Boolean]: True if validation succeeded, False otherwise
44
- def self.validate(habitat, configurator)
44
+ def self.validate(_habitat, _configurator)
45
45
  ok = true
46
46
  ok
47
47
  end
@@ -383,9 +383,9 @@ module MU
383
383
 
384
384
  # Generic pre-processing of {MU::Config::BasketofKittens::loadbalancers}, bare and unvalidated.
385
385
  # @param lb [Hash]: The resource to process and validate
386
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
386
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
387
387
  # @return [Boolean]: True if validation succeeded, False otherwise
388
- def self.validate(lb, configurator)
388
+ def self.validate(lb, _configurator)
389
389
  ok = true
390
390
  # Convert old-school listener declarations into target groups and health
391
391
  # checks, for which AWS and Google both have equivalents.
@@ -446,7 +446,7 @@ module MU
446
446
  else
447
447
  found = false
448
448
  lb['targetgroups'].each { |tg|
449
- if l['targetgroup'] == action['targetgroup']
449
+ if tg['name'] == action['targetgroup']
450
450
  found = true
451
451
  break
452
452
  end
@@ -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
@@ -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
@@ -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
@@ -51,9 +51,9 @@ 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
  if notifier['subscriptions']
@@ -0,0 +1,333 @@
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
+ kitten(shallow: true) if @mommacat # try to populate the actual cloud object for this
125
+ end
126
+
127
+ # Comparison operator
128
+ def <=>(other)
129
+ return 1 if other.nil?
130
+ self.to_s <=> other.to_s
131
+ end
132
+
133
+ # 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}.
134
+ # @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
135
+ # @return [Hash]
136
+ def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [])
137
+ parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
138
+ desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
139
+ schema = {
140
+ "type" => "object",
141
+ "#MU_REFERENCE" => true,
142
+ "minProperties" => 1,
143
+ "description" => desc,
144
+ "properties" => {
145
+ "id" => {
146
+ "type" => "string",
147
+ "description" => "Cloud identifier of a resource we want to reference, typically used when leveraging resources not managed by MU"
148
+ },
149
+ "name" => {
150
+ "type" => "string",
151
+ "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+."
152
+ },
153
+ "type" => {
154
+ "type" => "string",
155
+ "description" => "The resource type we're attempting to reference.",
156
+ "enum" => MU::Cloud.resource_types.values.map { |t| t[:cfg_plural] }
157
+ },
158
+ "deploy_id" => {
159
+ "type" => "string",
160
+ "description" => "Our target resource should be found in this Mu deploy."
161
+ },
162
+ "credentials" => MU::Config.credentials_primitive,
163
+ "region" => MU::Config.region_primitive,
164
+ "cloud" => MU::Config.cloud_primitive,
165
+ "tag" => {
166
+ "type" => "object",
167
+ "description" => "If the target resource supports tagging and our resource implementations +find+ method supports it, we can attempt to locate it by tag.",
168
+ "properties" => {
169
+ "key" => {
170
+ "type" => "string",
171
+ "description" => "The tag or label key to search against"
172
+ },
173
+ "value" => {
174
+ "type" => "string",
175
+ "description" => "The tag or label value to match"
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ if !["folders", "habitats"].include?(type)
182
+ schema["properties"]["habitat"] = MU::Config::Habitat.reference
183
+ end
184
+
185
+ if omit_fields
186
+ omit_fields.each { |f|
187
+ schema["properties"].delete(f)
188
+ }
189
+ end
190
+
191
+ if !type.nil?
192
+ schema["required"] = ["type"]
193
+ schema["properties"]["type"]["default"] = type
194
+ schema["properties"]["type"]["enum"] = [type]
195
+ end
196
+
197
+ aliases.each { |a|
198
+ a.each_pair { |k, v|
199
+ if schema["properties"][v]
200
+ schema["properties"][k] = schema["properties"][v].dup
201
+ schema["properties"][k]["description"] = "Alias for <tt>#{v}</tt>"
202
+ else
203
+ MU.log "Reference schema alias #{k} wants to alias #{v}, but no such attribute exists", MU::WARN, details: caller[4]
204
+ end
205
+ }
206
+ }
207
+
208
+ schema
209
+ end
210
+
211
+ # Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
212
+ # of the sort that would have been used to declare this reference in the
213
+ # first place.
214
+ def to_h
215
+ me = { }
216
+
217
+ self.instance_variables.each { |var|
218
+ next if [:@obj, :@mommacat, :@tag_key, :@tag_value].include?(var)
219
+ val = self.instance_variable_get(var)
220
+ next if val.nil?
221
+ val = val.to_h if val.is_a?(MU::Config::Ref)
222
+ me[var.to_s.sub(/^@/, '')] = val
223
+ }
224
+ if @tag_key and !@tag_key.empty?
225
+ me['tag'] = {
226
+ 'key' => @tag_key,
227
+ 'value' => @tag_value
228
+ }
229
+ end
230
+ me
231
+ end
232
+
233
+ # Getter for the #{id} instance variable that attempts to populate it if
234
+ # it's not set.
235
+ # @return [String,nil]
236
+ def id
237
+ return @id if @id
238
+ kitten # if it's not defined, attempt to define it
239
+ @id
240
+ end
241
+
242
+ # Alias for {id}
243
+ # @return [String,nil]
244
+ def cloud_id
245
+ id
246
+ end
247
+
248
+ # Return a {MU::Cloud} object for this reference. This is only meant to be
249
+ # called in a live deploy, which is to say that if called during initial
250
+ # configuration parsing, results may be incorrect.
251
+ # @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
252
+ def kitten(mommacat = @mommacat, shallow: false)
253
+ return nil if !@cloud or !@type
254
+
255
+ if @obj
256
+ @deploy_id ||= @obj.deploy_id
257
+ @id ||= @obj.cloud_id
258
+ @name ||= @obj.config['name']
259
+ return @obj
260
+ end
261
+
262
+ if mommacat
263
+ @obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: false)
264
+ if @obj # initialize missing attributes, if we can
265
+ @id ||= @obj.cloud_id
266
+ @mommacat ||= mommacat
267
+ @obj.intoDeploy(@mommacat) # make real sure these are set
268
+ @deploy_id ||= mommacat.deploy_id
269
+ if !@name
270
+ if @obj.config and @obj.config['name']
271
+ @name = @obj.config['name']
272
+ elsif @obj.mu_name
273
+ if @type == "folders"
274
+ MU.log "would assign name '#{@obj.mu_name}' in ref to this folder if I were feeling aggressive", MU::WARN, details: self.to_h
275
+ end
276
+ # @name = @obj.mu_name
277
+ end
278
+ end
279
+ return @obj
280
+ else
281
+ # MU.log "Failed to find a live '#{@type.to_s}' object named #{@name}#{@id ? " (#{@id})" : "" }#{ @habitat ? " in habitat #{@habitat}" : "" }", MU::WARN, details: self
282
+ end
283
+ end
284
+
285
+ if !@obj and !(@cloud == "Google" and @id and @type == "users" and MU::Cloud::Google::User.cannedServiceAcctName?(@id)) and !shallow
286
+
287
+ begin
288
+ hab_arg = if @habitat.nil?
289
+ [nil]
290
+ elsif @habitat.is_a?(MU::Config::Ref)
291
+ [@habitat.id]
292
+ elsif @habitat.is_a?(Hash)
293
+ [@habitat["id"]]
294
+ else
295
+ [@habitat.to_s]
296
+ end
297
+
298
+ found = MU::MommaCat.findStray(
299
+ @cloud,
300
+ @type,
301
+ name: @name,
302
+ cloud_id: @id,
303
+ deploy_id: @deploy_id,
304
+ region: @region,
305
+ habitats: hab_arg,
306
+ credentials: @credentials,
307
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
308
+ )
309
+ @obj ||= found.first if found
310
+ rescue ThreadError => e
311
+ # Sometimes MommaCat calls us in a potential deadlock situation;
312
+ # don't be the cause of a fatal error if so, we don't need this
313
+ # object that badly.
314
+ raise e if !e.message.match(/recursive locking/)
315
+ rescue SystemExit
316
+ # XXX this is temporary, to cope with some debug stuff that's in findStray
317
+ # for the nonce
318
+ return
319
+ end
320
+ end
321
+
322
+ if @obj
323
+ @deploy_id ||= @obj.deploy_id
324
+ @id ||= @obj.cloud_id
325
+ @name ||= @obj.config['name']
326
+ end
327
+
328
+ @obj
329
+ end
330
+
331
+ end
332
+ end
333
+ end