cloud-mu 2.0.0.pre.beta2 → 2.0.0.pre.beta3

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile.lock +1 -1
  3. data/cloud-mu.gemspec +4 -3
  4. data/cookbooks/mu-master/templates/default/mu.rc.erb +2 -2
  5. data/cookbooks/mu-tools/files/default/Mu_CA.pem +18 -19
  6. data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -1
  7. data/modules/mu/cleanup.rb +14 -1
  8. data/modules/mu/cloud.rb +40 -22
  9. data/modules/mu/clouds/aws/alarm.rb +6 -0
  10. data/modules/mu/clouds/aws/bucket.rb +29 -0
  11. data/modules/mu/clouds/aws/cache_cluster.rb +6 -0
  12. data/modules/mu/clouds/aws/container_cluster.rb +6 -0
  13. data/modules/mu/clouds/aws/database.rb +6 -0
  14. data/modules/mu/clouds/aws/dnszone.rb +6 -0
  15. data/modules/mu/clouds/aws/endpoint.rb +6 -0
  16. data/modules/mu/clouds/aws/firewall_rule.rb +6 -0
  17. data/modules/mu/clouds/aws/folder.rb +6 -0
  18. data/modules/mu/clouds/aws/function.rb +6 -0
  19. data/modules/mu/clouds/aws/group.rb +6 -0
  20. data/modules/mu/clouds/aws/loadbalancer.rb +6 -0
  21. data/modules/mu/clouds/aws/log.rb +6 -0
  22. data/modules/mu/clouds/aws/msg_queue.rb +6 -0
  23. data/modules/mu/clouds/aws/nosqldb.rb +6 -0
  24. data/modules/mu/clouds/aws/notifier.rb +6 -0
  25. data/modules/mu/clouds/aws/role.rb +97 -11
  26. data/modules/mu/clouds/aws/search_domain.rb +6 -0
  27. data/modules/mu/clouds/aws/server.rb +6 -0
  28. data/modules/mu/clouds/aws/server_pool.rb +6 -0
  29. data/modules/mu/clouds/aws/storage_pool.rb +6 -0
  30. data/modules/mu/clouds/aws/user.rb +6 -0
  31. data/modules/mu/clouds/aws/vpc.rb +25 -1
  32. data/modules/mu/clouds/google.rb +86 -16
  33. data/modules/mu/clouds/google/bucket.rb +78 -3
  34. data/modules/mu/clouds/google/container_cluster.rb +12 -0
  35. data/modules/mu/clouds/google/database.rb +15 -1
  36. data/modules/mu/clouds/google/firewall_rule.rb +18 -2
  37. data/modules/mu/clouds/google/folder.rb +183 -16
  38. data/modules/mu/clouds/google/group.rb +7 -1
  39. data/modules/mu/clouds/google/habitat.rb +139 -24
  40. data/modules/mu/clouds/google/loadbalancer.rb +26 -12
  41. data/modules/mu/clouds/google/server.rb +25 -10
  42. data/modules/mu/clouds/google/server_pool.rb +16 -3
  43. data/modules/mu/clouds/google/user.rb +7 -1
  44. data/modules/mu/clouds/google/vpc.rb +87 -76
  45. data/modules/mu/config.rb +12 -0
  46. data/modules/mu/config/bucket.rb +4 -0
  47. data/modules/mu/config/folder.rb +1 -0
  48. data/modules/mu/config/habitat.rb +1 -1
  49. data/modules/mu/config/role.rb +78 -34
  50. data/modules/mu/config/vpc.rb +1 -0
  51. data/modules/mu/groomers/chef.rb +1 -1
  52. data/modules/mu/kittens.rb +689 -283
  53. metadata +5 -4
@@ -19,6 +19,7 @@ module MU
19
19
  class ServerPool < MU::Cloud::ServerPool
20
20
 
21
21
  @deploy = nil
22
+ @project_id = nil
22
23
  @config = nil
23
24
  attr_reader :mu_name
24
25
  attr_reader :cloud_id
@@ -32,6 +33,11 @@ module MU
32
33
  @cloud_id ||= cloud_id
33
34
  if !mu_name.nil?
34
35
  @mu_name = mu_name
36
+ @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
37
+ if !@project_id
38
+ project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
39
+ @project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
40
+ end
35
41
  elsif @config['scrub_mu_isms']
36
42
  @mu_name = @config['name']
37
43
  else
@@ -41,6 +47,7 @@ module MU
41
47
 
42
48
  # Called automatically by {MU::Deploy#createResources}
43
49
  def create
50
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
44
51
  port_objs = []
45
52
 
46
53
  @config['named_ports'].each { |port_cfg|
@@ -95,7 +102,7 @@ module MU
95
102
 
96
103
  MU.log "Creating instance template #{@mu_name}", details: template_obj
97
104
  template = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_instance_template(
98
- @config['project'],
105
+ @project_id,
99
106
  template_obj
100
107
  )
101
108
 
@@ -117,7 +124,7 @@ module MU
117
124
 
118
125
  MU.log "Creating region instance group manager #{@mu_name}", details: mgr_obj
119
126
  mgr = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_region_instance_group_manager(
120
- @config['project'],
127
+ @project_id,
121
128
  @config['region'],
122
129
  mgr_obj
123
130
  )
@@ -143,7 +150,7 @@ module MU
143
150
 
144
151
  MU.log "Creating autoscaler policy #{@mu_name}", details: scaler_obj
145
152
  MU::Cloud::Google.compute(credentials: @config['credentials']).insert_region_autoscaler(
146
- @config['project'],
153
+ @project_id,
147
154
  @config['region'],
148
155
  scaler_obj
149
156
  )
@@ -250,6 +257,12 @@ module MU
250
257
  false
251
258
  end
252
259
 
260
+ # Denote whether this resource implementation is experiment, ready for
261
+ # testing, or ready for production use.
262
+ def self.quality
263
+ MU::Cloud::RELEASE
264
+ end
265
+
253
266
  # Remove all autoscale groups associated with the currently loaded deployment.
254
267
  # @param noop [Boolean]: If true, will only print what would be done
255
268
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -110,6 +110,12 @@ module MU
110
110
  true
111
111
  end
112
112
 
113
+ # Denote whether this resource implementation is experiment, ready for
114
+ # testing, or ready for production use.
115
+ def self.quality
116
+ MU::Cloud::ALPHA
117
+ end
118
+
113
119
  # Remove all users associated with the currently loaded deployment.
114
120
  # @param noop [Boolean]: If true, will only print what would be done
115
121
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -142,7 +148,7 @@ module MU
142
148
  # @param region [String]: The cloud provider region.
143
149
  # @param flags [Hash]: Optional flags
144
150
  # @return [OpenStruct]: The cloud provider's complete descriptions of matching user group.
145
- def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
151
+ def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {}, tag_key: nil, tag_value: nil)
146
152
  flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
147
153
  found = nil
148
154
  resp = MU::Cloud::Google.iam(credentials: credentials).list_project_service_accounts(
@@ -21,6 +21,7 @@ module MU
21
21
 
22
22
  @deploy = nil
23
23
  @config = nil
24
+ @project_id = nil
24
25
  attr_reader :mu_name
25
26
  attr_reader :cloud_id
26
27
  attr_reader :url
@@ -45,6 +46,11 @@ module MU
45
46
  if @cloud_id.nil? or @cloud_id.empty?
46
47
  @cloud_id = MU::Cloud::Google.nameStr(@mu_name)
47
48
  end
49
+ @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
50
+ if !@project_id
51
+ project = MU::Cloud::Google.projectLookup(@config['project'], @deploy, sibling_only: true, raise_on_fail: false)
52
+ @project_id = project.nil? ? @config['project'] : project.cloudobj.cloud_id
53
+ end
48
54
  loadSubnets
49
55
  elsif @config['scrub_mu_isms']
50
56
  @mu_name = @config['name']
@@ -56,14 +62,17 @@ module MU
56
62
 
57
63
  # Called automatically by {MU::Deploy#createResources}
58
64
  def create
65
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
66
+
59
67
  networkobj = MU::Cloud::Google.compute(:Network).new(
60
68
  name: MU::Cloud::Google.nameStr(@mu_name),
61
69
  description: @deploy.deploy_id,
62
70
  auto_create_subnetworks: false
63
71
  # i_pv4_range: @config['ip_block']
64
72
  )
65
- MU.log "Creating network #{@mu_name} (#{@config['ip_block']}) in project #{@config['project']}", details: networkobj
66
- resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_network(@config['project'], networkobj)
73
+ MU.log "Creating network #{@mu_name} (#{@config['ip_block']}) in project #{@project_id}", details: networkobj
74
+
75
+ resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_network(@project_id, networkobj)
67
76
  @url = resp.self_link # XXX needs to go in notify
68
77
  @cloud_id = resp.name
69
78
 
@@ -75,7 +84,7 @@ module MU
75
84
  MU.dupGlobals(parent_thread_id)
76
85
  subnet_name = @config['name']+"-"+subnet['name']
77
86
  subnet_mu_name = MU::Cloud::Google.nameStr(@deploy.getResourceName(subnet_name))
78
- MU.log "Creating subnetwork #{subnet_mu_name} (#{subnet['ip_block']}) in project #{@config['project']}", details: subnet
87
+ MU.log "Creating subnetwork #{subnet_mu_name} (#{subnet['ip_block']}) in project #{@project_id}", details: subnet
79
88
  subnetobj = MU::Cloud::Google.compute(:Subnetwork).new(
80
89
  name: subnet_mu_name,
81
90
  description: @deploy.deploy_id,
@@ -83,7 +92,7 @@ module MU
83
92
  network: @url,
84
93
  region: subnet['availability_zone']
85
94
  )
86
- resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_subnetwork(@config['project'], subnet['availability_zone'], subnetobj)
95
+ resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_subnetwork(@project_id, subnet['availability_zone'], subnetobj)
87
96
 
88
97
  }
89
98
  }
@@ -120,21 +129,16 @@ module MU
120
129
  def notify
121
130
  base = MU.structToHash(cloud_desc)
122
131
  base["cloud_id"] = @cloud_id
132
+ base["project_id"] = @project_id
123
133
  base.merge!(@config.to_h)
124
- if @config['name'] == "gkeprivate"
125
- pp base.keys
126
- puts base['cloud_id']
127
- end
128
-
129
134
  base
130
135
  end
131
136
 
132
137
  # Describe this VPC from the cloud platform's perspective
133
138
  # @return [Hash]
134
139
  def cloud_desc
135
- @config['project'] ||= MU::Cloud::Google.defaultProject(@config['credentials'])
136
140
 
137
- resp = MU::Cloud::Google.compute(credentials: @config['credentials']).get_network(@config['project'], @cloud_id)
141
+ resp = MU::Cloud::Google.compute(credentials: @config['credentials']).get_network(@project_id, @cloud_id)
138
142
  if @cloud_id.nil? or @cloud_id == ""
139
143
  MU.log "Couldn't describe #{self}, @cloud_id #{@cloud_id.nil? ? "undefined" : "empty" }", MU::ERR
140
144
  return nil
@@ -143,7 +147,7 @@ end
143
147
  resp = resp.to_h
144
148
  @url ||= resp[:self_link]
145
149
  routes = MU::Cloud::Google.compute(credentials: @config['credentials']).list_routes(
146
- @config['project'],
150
+ @project_id,
147
151
  filter: "network eq #{@cloud_id}"
148
152
  ).items
149
153
  resp[:routes] = routes.map { |r| r.to_h } if routes
@@ -154,6 +158,8 @@ end
154
158
 
155
159
  # Called automatically by {MU::Deploy#createResources}
156
160
  def groom
161
+ @project_id = MU::Cloud::Google.projectLookup(@config['project'], @deploy).cloudobj.cloud_id
162
+
157
163
  rtb = @config['route_tables'].first
158
164
 
159
165
  rtb['routes'].each { |route|
@@ -169,21 +175,6 @@ end
169
175
  @config['peers'].each { |peer|
170
176
  if peer['vpc']['vpc_name']
171
177
  peer_obj = @deploy.findLitterMate(name: peer['vpc']['vpc_name'], type: "vpcs")
172
- if peer_obj
173
- if peer_obj.config['peers']
174
- skipme = false
175
- peer_obj.config['peers'].each { |peerpeer|
176
- if peerpeer['vpc']['vpc_name'] == @config['name'] and
177
- (peer['vpc']['vpc_name'] <=> @config['name']) == -1
178
- skipme = true
179
- MU.log "VPCs #{peer['vpc']['vpc_name']} and #{@config['name']} both declare mutual peering connection, ignoring #{@config['name']}'s redundant declaration", MU::DEBUG
180
- # XXX and if deploy_id matches or is unset
181
- end
182
- }
183
- next if skipme
184
- end
185
- end
186
-
187
178
  else
188
179
  tag_key, tag_value = peer['vpc']['tag'].split(/=/, 2) if !peer['vpc']['tag'].nil?
189
180
  if peer['vpc']['deploy_id'].nil? and peer['vpc']['vpc_id'].nil? and tag_key.nil?
@@ -219,13 +210,20 @@ end
219
210
  peer_network: url
220
211
  )
221
212
 
222
- MU.log "Peering #{@url} with #{url}, connection name is #{cnxn_name}", details: peerreq
223
-
224
- MU::Cloud::Google.compute(credentials: @config['credentials']).add_network_peering(
225
- @config['project'],
226
- @cloud_id,
227
- peerreq
228
- )
213
+ begin
214
+ MU.log "Peering #{@cloud_id} with #{peer_obj.cloudobj.cloud_id}, connection name is #{cnxn_name}", details: peerreq
215
+ MU::Cloud::Google.compute(credentials: @config['credentials']).add_network_peering(
216
+ @project_id,
217
+ @cloud_id,
218
+ peerreq
219
+ )
220
+ rescue ::Google::Apis::ClientError => e
221
+ if e.message.match(/operation in progress on the local or peer network/)
222
+ MU.log e.message, MU::DEBUG, details: peerreq
223
+ sleep 10
224
+ retry
225
+ end
226
+ end
229
227
  count += 1
230
228
  }
231
229
  end
@@ -293,7 +291,7 @@ end
293
291
  resp = nil
294
292
  MU::Cloud::Google.listRegions(@config['us_only']).each { |r|
295
293
  resp = MU::Cloud::Google.compute(credentials: @config['credentials']).list_subnetworks(
296
- @config['project'],
294
+ @project_id,
297
295
  r,
298
296
  filter: "network eq #{network[:self_link]}"
299
297
  )
@@ -496,6 +494,12 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
496
494
  true
497
495
  end
498
496
 
497
+ # Denote whether this resource implementation is experiment, ready for
498
+ # testing, or ready for production use.
499
+ def self.quality
500
+ MU::Cloud::RELEASE
501
+ end
502
+
499
503
  # Remove all VPC resources associated with the currently loaded deployment.
500
504
  # @param noop [Boolean]: If true, will only print what would be done
501
505
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -543,6 +547,7 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
543
547
  def self.validateConfig(vpc, configurator)
544
548
  ok = true
545
549
 
550
+
546
551
  if vpc['create_standard_subnets']
547
552
  # Manufacture some generic routes, if applicable.
548
553
  if !vpc['route_tables'] or vpc['route_tables'].empty?
@@ -557,46 +562,46 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
557
562
  }
558
563
  ]
559
564
  end
560
-
561
- # Generate a set of subnets per route, if none are declared
562
- if !vpc['subnets'] or vpc['subnets'].empty?
563
- if vpc['regions'].nil? or vpc['regions'].empty?
564
- vpc['regions'] = MU::Cloud::Google.listRegions(vpc['us_only'])
565
- end
566
- blocks = configurator.divideNetwork(vpc['ip_block'], vpc['regions'].size*vpc['route_tables'].size, 29)
567
- ok = false if blocks.nil?
568
-
569
- vpc["subnets"] = []
570
- vpc['route_tables'].each { |t|
571
- count = 0
572
- vpc['regions'].each { |r|
573
- block = blocks.shift
574
- vpc["subnets"] << {
575
- "availability_zone" => r,
576
- "route_table" => t["name"],
577
- "ip_block" => block.to_s,
578
- "name" => "Subnet"+count.to_s+t["name"].capitalize,
579
- "map_public_ips" => true
565
+ else
566
+ # If create_standard_subnets is off, and no route_tables were
567
+ # declared at all, let's assume we want purely self-contained
568
+ # private VPC, and create a dummy route accordingly.
569
+ vpc['route_tables'] ||= [
570
+ {
571
+ "name" => "private",
572
+ "routes" => [
573
+ {
574
+ "destination_network" => "0.0.0.0/0"
580
575
  }
581
- count = count + 1
582
- }
576
+ ]
583
577
  }
584
- end
578
+ ]
585
579
  end
586
580
 
587
- # If create_standard_subnets is off, and no route_tables were
588
- # declared at all, let's assume we want purely self-contained
589
- # private VPCs
590
- vpc['route_tables'] ||= [
591
- {
592
- "name" => "private",
593
- "routes" => [
594
- {
595
- "destination_network" => "0.0.0.0/0"
581
+ # Generate a set of subnets per route, if none are declared
582
+ if !vpc['subnets'] or vpc['subnets'].empty?
583
+ if vpc['regions'].nil? or vpc['regions'].empty?
584
+ vpc['regions'] = MU::Cloud::Google.listRegions(vpc['us_only'])
585
+ end
586
+ blocks = configurator.divideNetwork(vpc['ip_block'], vpc['regions'].size*vpc['route_tables'].size, 29)
587
+ ok = false if blocks.nil?
588
+
589
+ vpc["subnets"] = []
590
+ vpc['route_tables'].each { |t|
591
+ count = 0
592
+ vpc['regions'].each { |r|
593
+ block = blocks.shift
594
+ vpc["subnets"] << {
595
+ "availability_zone" => r,
596
+ "route_table" => t["name"],
597
+ "ip_block" => block.to_s,
598
+ "name" => "Subnet"+count.to_s+t["name"].capitalize,
599
+ "map_public_ips" => true
596
600
  }
597
- ]
601
+ count = count + 1
602
+ }
598
603
  }
599
- ]
604
+ end
600
605
 
601
606
  # Google VPCs can't have routes that are anything other than global
602
607
  # (they can be tied to individual instances by tags, but w/e). So we
@@ -617,7 +622,8 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
617
622
  "ip_block" => blocks.shift,
618
623
  "route_tables" => [tbl],
619
624
  "parent_block" => vpc['ip_block'],
620
- "subnets" => []
625
+ "subnets" => [],
626
+ "peers" => vpc['peers']
621
627
  }
622
628
  MU.log "Splitting VPC #{newvpc['name']} off from #{vpc['name']}", MU::NOTICE
623
629
 
@@ -626,11 +632,16 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
626
632
  newvpc[key] = val
627
633
  }
628
634
  newvpc['peers'] ||= []
635
+ # Add the peer connections we're generating, in addition
629
636
  peernames.each { |peer|
630
- if peer != vpc['name']+"-"+tbl['name']
637
+ if peer != newvpc['name']
631
638
  newvpc['peers'] << { "vpc" => { "vpc_name" => peer } }
632
639
  end
633
640
  }
641
+ newvpc['peers'].reject! { |p|
642
+ p.values.first['vpc_name'] == newvpc['name'] or p.values.first['vpc_name'] == vpc['name']
643
+ }
644
+
634
645
  vpc["subnets"].each { |subnet|
635
646
  newvpc["subnets"] << subnet if subnet["route_table"] == tbl["name"]
636
647
  }
@@ -771,7 +782,7 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
771
782
  # several other cases missing for various types of routers (raw IPs, instance ids, etc) XXX
772
783
  elsif route['gateway'] == "#DENY"
773
784
  resp = MU::Cloud::Google.compute(credentials: @config['credentials']).list_routes(
774
- @config['project'],
785
+ @project_id,
775
786
  filter: "network eq #{network}"
776
787
  )
777
788
 
@@ -779,7 +790,7 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
779
790
  resp.items.each { |r|
780
791
  next if r.next_hop_gateway.nil? or !r.next_hop_gateway.match(/\/global\/gateways\/default-internet-gateway$/)
781
792
  MU.log "Removing standard route #{r.name} per our #DENY entry"
782
- MU::Cloud::Google.compute(credentials: @config['credentials']).delete_route(@config['project'], r.name)
793
+ MU::Cloud::Google.compute(credentials: @config['credentials']).delete_route(@project_id, r.name)
783
794
  }
784
795
  end
785
796
  elsif route['gateway'] == "#INTERNET"
@@ -796,11 +807,11 @@ MU.log "ROUTES TO #{target_instance.name}", MU::WARN, details: resp
796
807
 
797
808
  if route['gateway'] != "#DENY" and routeobj
798
809
  begin
799
- MU::Cloud::Google.compute(credentials: @config['credentials']).get_route(@config['project'], routename)
810
+ MU::Cloud::Google.compute(credentials: @config['credentials']).get_route(@project_id, routename)
800
811
  rescue ::Google::Apis::ClientError, MU::MuError => e
801
812
  if e.message.match(/notFound/)
802
- MU.log "Creating route #{routename} in project #{@config['project']}", details: routeobj
803
- resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_route(@config['project'], routeobj)
813
+ MU.log "Creating route #{routename} in project #{@project_id}", details: routeobj
814
+ resp = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_route(@project_id, routeobj)
804
815
  else
805
816
  # TODO can't update GCP routes, would have to delete and re-create
806
817
  end
@@ -156,6 +156,8 @@ module MU
156
156
  end
157
157
  res_class = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(classname)
158
158
  required, res_schema = res_class.schema(self)
159
+ docschema["properties"][attrs[:cfg_plural]]["items"]["description"] ||= ""
160
+ docschema["properties"][attrs[:cfg_plural]]["items"]["description"] += "\n#\n# `#{cloud}`: "+res_class.quality
159
161
  res_schema.each { |key, cfg|
160
162
  if !docschema["properties"][attrs[:cfg_plural]]["items"]["properties"][key]
161
163
  only_children[attrs[:cfg_plural]] ||= {}
@@ -888,6 +890,16 @@ module MU
888
890
  end
889
891
  end
890
892
 
893
+ if descriptor['project']
894
+ if haveLitterMate?(descriptor['project'], "habitats")
895
+ descriptor['dependencies'] ||= []
896
+ descriptor['dependencies'] << {
897
+ "type" => "habitat",
898
+ "name" => descriptor['project']
899
+ }
900
+ end
901
+ end
902
+
891
903
  # Does this resource go in a VPC?
892
904
  if !descriptor["vpc"].nil? and !delay_validation
893
905
  descriptor['vpc']['cloud'] = descriptor['cloud']
@@ -49,6 +49,10 @@ module MU
49
49
  "type" => "string",
50
50
  "default" => "index.html",
51
51
  "description" => "If +web_enabled+, return this object when \"diretory\" (a path not ending in a key/object) is invoked."
52
+ },
53
+ "policies" => {
54
+ "type" => "array",
55
+ "items" => MU::Config::Role.policy_primitive(subobjects: true, grant_to: true, permissions_optional: true)
52
56
  }
53
57
  }
54
58
  }
@@ -26,6 +26,7 @@ module MU
26
26
  "description" => "Set up a cloud provider folder/OU for containing other account-level resources",
27
27
  "properties" => {
28
28
  "name" => { "type" => "string" },
29
+ "parent" => MU::Config::Folder.reference
29
30
  }
30
31
  }
31
32
  end
@@ -26,7 +26,7 @@ module MU
26
26
  "description" => "Generate a cloud habitat (AWS account, Google Cloud project, Azure Directory, etc)",
27
27
  "properties" => {
28
28
  "name" => { "type" => "string" },
29
- "folder" => MU::Config::Folder.reference
29
+ "parent" => MU::Config::Folder.reference
30
30
  }
31
31
  }
32
32
  end