cloud-mu 3.1.6 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +15 -12
  4. data/bin/mu-azure-tests +57 -0
  5. data/bin/mu-cleanup +2 -4
  6. data/bin/mu-configure +37 -1
  7. data/bin/mu-deploy +3 -3
  8. data/bin/mu-findstray-tests +25 -0
  9. data/bin/mu-gen-docs +2 -4
  10. data/bin/mu-load-config.rb +2 -1
  11. data/bin/mu-run-tests +37 -12
  12. data/cloud-mu.gemspec +4 -4
  13. data/cookbooks/mu-tools/attributes/default.rb +7 -0
  14. data/cookbooks/mu-tools/libraries/helper.rb +87 -3
  15. data/cookbooks/mu-tools/recipes/apply_security.rb +39 -23
  16. data/cookbooks/mu-tools/recipes/aws_api.rb +13 -0
  17. data/cookbooks/mu-tools/recipes/google_api.rb +4 -0
  18. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  19. data/cookbooks/mu-tools/resources/disk.rb +33 -12
  20. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  21. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  22. data/extras/clean-stock-amis +10 -2
  23. data/extras/generate-stock-images +7 -3
  24. data/extras/image-generators/AWS/centos7.yaml +19 -16
  25. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  26. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  27. data/modules/mommacat.ru +2 -2
  28. data/modules/mu.rb +84 -97
  29. data/modules/mu/adoption.rb +359 -59
  30. data/modules/mu/cleanup.rb +67 -44
  31. data/modules/mu/cloud.rb +108 -1754
  32. data/modules/mu/cloud/database.rb +49 -0
  33. data/modules/mu/cloud/dnszone.rb +44 -0
  34. data/modules/mu/cloud/machine_images.rb +212 -0
  35. data/modules/mu/cloud/providers.rb +81 -0
  36. data/modules/mu/cloud/resource_base.rb +929 -0
  37. data/modules/mu/cloud/server.rb +40 -0
  38. data/modules/mu/cloud/server_pool.rb +1 -0
  39. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  40. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  41. data/modules/mu/cloud/wrappers.rb +178 -0
  42. data/modules/mu/config.rb +122 -80
  43. data/modules/mu/config/alarm.rb +2 -6
  44. data/modules/mu/config/bucket.rb +32 -3
  45. data/modules/mu/config/cache_cluster.rb +2 -2
  46. data/modules/mu/config/cdn.rb +100 -0
  47. data/modules/mu/config/collection.rb +1 -1
  48. data/modules/mu/config/container_cluster.rb +2 -2
  49. data/modules/mu/config/database.rb +84 -105
  50. data/modules/mu/config/database.yml +1 -2
  51. data/modules/mu/config/dnszone.rb +5 -4
  52. data/modules/mu/config/doc_helpers.rb +4 -5
  53. data/modules/mu/config/endpoint.rb +2 -1
  54. data/modules/mu/config/firewall_rule.rb +3 -19
  55. data/modules/mu/config/folder.rb +1 -1
  56. data/modules/mu/config/function.rb +17 -8
  57. data/modules/mu/config/group.rb +1 -1
  58. data/modules/mu/config/habitat.rb +1 -1
  59. data/modules/mu/config/job.rb +89 -0
  60. data/modules/mu/config/loadbalancer.rb +57 -11
  61. data/modules/mu/config/log.rb +1 -1
  62. data/modules/mu/config/msg_queue.rb +1 -1
  63. data/modules/mu/config/nosqldb.rb +1 -1
  64. data/modules/mu/config/notifier.rb +8 -19
  65. data/modules/mu/config/ref.rb +81 -9
  66. data/modules/mu/config/role.rb +1 -1
  67. data/modules/mu/config/schema_helpers.rb +30 -34
  68. data/modules/mu/config/search_domain.rb +1 -1
  69. data/modules/mu/config/server.rb +5 -13
  70. data/modules/mu/config/server_pool.rb +3 -7
  71. data/modules/mu/config/storage_pool.rb +1 -1
  72. data/modules/mu/config/tail.rb +10 -0
  73. data/modules/mu/config/user.rb +1 -1
  74. data/modules/mu/config/vpc.rb +13 -17
  75. data/modules/mu/defaults/AWS.yaml +106 -106
  76. data/modules/mu/defaults/Azure.yaml +1 -0
  77. data/modules/mu/defaults/Google.yaml +1 -0
  78. data/modules/mu/deploy.rb +33 -19
  79. data/modules/mu/groomer.rb +15 -0
  80. data/modules/mu/groomers/chef.rb +3 -0
  81. data/modules/mu/logger.rb +120 -144
  82. data/modules/mu/master.rb +22 -1
  83. data/modules/mu/mommacat.rb +71 -26
  84. data/modules/mu/mommacat/daemon.rb +23 -14
  85. data/modules/mu/mommacat/naming.rb +82 -3
  86. data/modules/mu/mommacat/search.rb +59 -16
  87. data/modules/mu/mommacat/storage.rb +119 -48
  88. data/modules/mu/{clouds → providers}/README.md +1 -1
  89. data/modules/mu/{clouds → providers}/aws.rb +248 -62
  90. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  91. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  92. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  93. data/modules/mu/providers/aws/cdn.rb +782 -0
  94. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  95. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +65 -63
  96. data/modules/mu/providers/aws/database.rb +1747 -0
  97. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  98. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  99. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  100. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  101. data/modules/mu/{clouds → providers}/aws/function.rb +291 -133
  102. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  103. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  104. data/modules/mu/providers/aws/job.rb +469 -0
  105. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  106. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  107. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  108. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  109. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  110. data/modules/mu/{clouds → providers}/aws/role.rb +112 -78
  111. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  112. data/modules/mu/{clouds → providers}/aws/server.rb +120 -145
  113. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  114. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  115. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  116. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  117. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  118. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  119. data/modules/mu/{clouds → providers}/aws/vpc.rb +141 -73
  120. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  121. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  122. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  123. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  124. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  126. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  127. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  128. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  129. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  130. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  132. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  133. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  134. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  135. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  136. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  137. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  138. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  142. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  143. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  144. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  145. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  146. data/modules/mu/{clouds → providers}/google.rb +15 -6
  147. data/modules/mu/{clouds → providers}/google/bucket.rb +2 -2
  148. data/modules/mu/{clouds → providers}/google/container_cluster.rb +29 -14
  149. data/modules/mu/{clouds → providers}/google/database.rb +2 -9
  150. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +3 -3
  151. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  152. data/modules/mu/{clouds → providers}/google/function.rb +4 -4
  153. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  154. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  155. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +2 -2
  156. data/modules/mu/{clouds → providers}/google/role.rb +46 -35
  157. data/modules/mu/{clouds → providers}/google/server.rb +26 -11
  158. data/modules/mu/{clouds → providers}/google/server_pool.rb +11 -11
  159. data/modules/mu/{clouds → providers}/google/user.rb +32 -22
  160. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  161. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  162. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  163. data/modules/mu/{clouds → providers}/google/vpc.rb +38 -3
  164. data/modules/tests/aws-jobs-functions.yaml +46 -0
  165. data/modules/tests/centos6.yaml +15 -0
  166. data/modules/tests/centos7.yaml +15 -0
  167. data/modules/tests/centos8.yaml +12 -0
  168. data/modules/tests/ecs.yaml +2 -2
  169. data/modules/tests/eks.yaml +1 -1
  170. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  171. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  172. data/modules/tests/microservice_app.yaml +288 -0
  173. data/modules/tests/rds.yaml +108 -0
  174. data/modules/tests/regrooms/rds.yaml +123 -0
  175. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  176. data/modules/tests/super_complex_bok.yml +2 -2
  177. data/modules/tests/super_simple_bok.yml +2 -2
  178. data/spec/mu/clouds/azure_spec.rb +2 -2
  179. metadata +126 -98
  180. data/modules/mu/clouds/aws/database.rb +0 -1974
  181. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -18,7 +18,7 @@ module MU
18
18
  class AWS
19
19
  # A firewall ruleset as configured in {MU::Config::BasketofKittens::firewall_rules}
20
20
  class FirewallRule < MU::Cloud::FirewallRule
21
- require "mu/clouds/aws/vpc"
21
+ require "mu/providers/aws/vpc"
22
22
 
23
23
  @admin_sgs = Hash.new
24
24
  @admin_sg_semaphore = Mutex.new
@@ -381,14 +381,14 @@ module MU
381
381
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
382
382
  # @param region [String]: The cloud provider region
383
383
  # @return [void]
384
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
384
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
385
385
  filters = if flags and flags["vpc_id"]
386
386
  [
387
387
  {name: "vpc-id", values: [flags["vpc_id"]]}
388
388
  ]
389
389
  else
390
390
  filters = [
391
- {name: "tag:MU-ID", values: [MU.deploy_id]}
391
+ {name: "tag:MU-ID", values: [deploy_id]}
392
392
  ]
393
393
  if !ignoremaster
394
394
  filters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
@@ -398,7 +398,7 @@ module MU
398
398
 
399
399
  # Some services create sneaky rogue ENIs which then block removal of
400
400
  # associated security groups. Find them and fry them.
401
- MU::Cloud::AWS::VPC.purge_interfaces(noop, filters, region: region, credentials: credentials)
401
+ MU::Cloud.resourceClass("AWS", "VPC").purge_interfaces(noop, filters, region: region, credentials: credentials)
402
402
 
403
403
  resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_security_groups(
404
404
  filters: filters
@@ -421,7 +421,7 @@ module MU
421
421
  # try to get out from under loose network interfaces with which
422
422
  # we're associated
423
423
  if sg.vpc_id
424
- default_sg = MU::Cloud::AWS::VPC.getDefaultSg(sg.vpc_id, region: region, credentials: credentials)
424
+ default_sg = MU::Cloud.resourceClass("AWS", "VPC").getDefaultSg(sg.vpc_id, region: region, credentials: credentials)
425
425
  if default_sg
426
426
  eni_resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_network_interfaces(
427
427
  filters: [ {name: "group-id", values: [sg.group_id]} ]
@@ -514,6 +514,31 @@ module MU
514
514
  end
515
515
  private_class_method :revoke_rules
516
516
 
517
+ # Return an AWS-specific chunk of schema commonly used in the +ingress_rules+ parameter of other resource types.
518
+ # @return [Hash]
519
+ def self.ingressRuleAddtlSchema
520
+ {
521
+ "items" => {
522
+ "properties" => {
523
+ "sgs" => {
524
+ "type" => "array",
525
+ "items" => {
526
+ "description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
527
+ "type" => "string"
528
+ }
529
+ },
530
+ "lbs" => {
531
+ "type" => "array",
532
+ "items" => {
533
+ "description" => "AWS Load Balancers which will have this rule applied to their traffic",
534
+ "type" => "string"
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ end
541
+
517
542
  # Cloud-specific configuration properties.
518
543
  # @param _config [MU::Config]: The calling MU::Config object
519
544
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -623,36 +648,16 @@ module MU
623
648
 
624
649
  if rule['firewall_rules']
625
650
  rule['firewall_rules'].each { |sg|
626
- if sg.is_a?(MU::Config::Ref) and sg.name
627
- acl["dependencies"] << {
628
- "type" => "firewall_rule",
629
- "name" => sg.name,
630
- "no_create_wait" => true
631
- }
632
- elsif sg['name'] and !sg['deploy_id']
633
- acl["dependencies"] << {
634
- "type" => "firewall_rule",
635
- "name" => sg['name'],
636
- "no_create_wait" => true
637
- }
651
+ if sg['name'] and !sg['deploy_id']
652
+ MU::Config.addDependency(acl, sg['name'], "firewall_rule", no_create_wait: true)
638
653
  end
639
654
  }
640
655
  end
641
656
 
642
657
  if rule['loadbalancers']
643
658
  rule['loadbalancers'].each { |lb|
644
- if lb.is_a?(MU::Config::Ref) and lb.name
645
- acl["dependencies"] << {
646
- "type" => "loadbalancer",
647
- "name" => lb.name,
648
- "phase" => "groom"
649
- }
650
- elsif lb['name'] and !lb['deploy_id']
651
- acl["dependencies"] << {
652
- "type" => "loadbalancer",
653
- "name" => lb['name'],
654
- "phase" => "groom"
655
- }
659
+ if lb['name'] and !lb['deploy_id']
660
+ MU::Config.addDependency(acl, lb['name'], "loadbalancer", phase: "groom")
656
661
  end
657
662
  }
658
663
  end
@@ -739,7 +744,6 @@ module MU
739
744
  # "ingress_rules" structure parsed and validated by MU::Config.
740
745
  #########################################################################
741
746
  def setRules(rules, add_to_self: false, ingress: true, egress: false)
742
- describe
743
747
  # XXX warn about attempt to set rules before we exist
744
748
  return if rules.nil? or rules.size == 0 or !@cloud_id
745
749
 
@@ -760,7 +764,7 @@ module MU
760
764
  ec2_rules = convertToEc2(rules)
761
765
  return if ec2_rules.nil?
762
766
 
763
- ext_permissions = MU.structToHash(cloud_desc.ip_permissions)
767
+ ext_permissions = MU.structToHash(cloud_desc(use_cache: false).ip_permissions)
764
768
 
765
769
  purge_extraneous_rules(ec2_rules, ext_permissions)
766
770
 
@@ -856,8 +860,11 @@ module MU
856
860
  p_start = rule['port'].to_i
857
861
  p_end = rule['port'].to_i
858
862
  elsif rule['proto'] != "icmp"
859
- raise MuError, "Can't create a TCP or UDP security group rule without specifying ports: #{rule}"
863
+ MU.log "Can't create a TCP or UDP security group rule without specifying ports, assuming 'all'", MU::WARN, details: rule
864
+ p_start = "0"
865
+ p_end = "65535"
860
866
  end
867
+
861
868
  if rule['proto'] != "icmp"
862
869
  if p_start.nil? or p_end.nil?
863
870
  raise MuError, "Got nil ports out of rule #{rule}"
@@ -59,7 +59,7 @@ module MU
59
59
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
60
60
  # @param region [String]: The cloud provider region
61
61
  # @return [void]
62
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
62
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
63
63
  end
64
64
 
65
65
  # Locate an existing AWS organization. If no identifying parameters are specified, this will return a description of the Organization which owns the account for our credentials.
@@ -18,6 +18,18 @@ module MU
18
18
  # A function as configured in {MU::Config::BasketofKittens::functions}
19
19
  class Function < MU::Cloud::Function
20
20
 
21
+ # If we have sibling resources in our deployment, automatically inject
22
+ # interesting things about them into our function's environment
23
+ # variables.
24
+ SIBLING_VARS = {
25
+ "servers" => ["private_ip_address", "public_ip_address"],
26
+ "search_domains" => ["endpoint"],
27
+ "databases" => ["endpoint"],
28
+ "endpoints" => ["url"],
29
+ "notifiers" => ["TopicArn"],
30
+ "nosqldbs" => ["table_arn"]
31
+ }
32
+
21
33
  # Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
22
34
  # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
23
35
  def initialize(**args)
@@ -42,92 +54,47 @@ module MU
42
54
 
43
55
  # Called automatically by {MU::Deploy#createResources}
44
56
  def create
45
- role_arn = get_role_arn(@config['iam_role'])
46
57
 
47
- lambda_properties = {
48
- code: {},
49
- function_name: @mu_name,
50
- handler: @config['handler'],
51
- publish: true,
52
- role: role_arn,
53
- runtime: @config['runtime'],
54
- }
58
+ lambda_properties = get_properties
55
59
 
56
- if @config['code']['zip_file']
57
- zip = File.read(@config['code']['zip_file'])
58
- MU.log "Uploading deployment package from #{@config['code']['zip_file']}"
59
- lambda_properties[:code][:zip_file] = zip
60
- else
61
- lambda_properties[:code][:s3_bucket] = @config['code']['s3_bucket']
62
- lambda_properties[:code][:s3_key] = @config['code']['s3_key']
63
- if @config['code']['s3_object_version']
64
- lambda_properties[:code][:s3_object_version] = @config['code']['s3_object_version']
65
- end
66
- end
67
-
68
- if @config.has_key?('timeout')
69
- lambda_properties[:timeout] = @config['timeout'].to_i ## secs
70
- end
71
-
72
- if @config.has_key?('memory')
73
- lambda_properties[:memory_size] = @config['memory'].to_i
74
- end
75
-
76
- if @config.has_key?('environment_variables')
77
- lambda_properties[:environment] = {
78
- variables: {@config['environment_variables'][0]['key'] => @config['environment_variables'][0]['value']}
79
- }
80
- end
81
-
82
- lambda_properties[:tags] = {}
83
- MU::MommaCat.listStandardTags.each_pair { |k, v|
84
- lambda_properties[:tags][k] = v
60
+ MU.retrier([Aws::Lambda::Errors::InvalidParameterValueException], max: 5, wait: 10) {
61
+ resp = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).create_function(lambda_properties)
62
+ @cloud_id = resp.function_name
85
63
  }
86
- if @config['tags']
87
- @config['tags'].each { |tag|
88
- lambda_properties[:tags][tag.key.first] = tag.values.first
89
- }
90
- end
91
64
 
92
- if @config.has_key?('vpc')
93
- sgs = []
94
- if @config['add_firewall_rules']
95
- @config['add_firewall_rules'].each { |sg|
96
- sg = @deploy.findLitterMate(type: "firewall_rule", name: sg['name'])
97
- sgs << sg.cloud_id if sg and sg.cloud_id
98
- }
99
- end
100
- if !@vpc
101
- raise MuError, "Function #{@config['name']} had a VPC configured, but none was loaded"
102
- end
103
- lambda_properties[:vpc_config] = {
104
- :subnet_ids => @vpc.subnets.map { |s| s.cloud_id },
105
- :security_group_ids => sgs
106
- }
107
- end
108
-
109
- retries = 0
110
- resp = begin
111
- MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).create_function(lambda_properties)
112
- rescue Aws::Lambda::Errors::InvalidParameterValueException => e
113
- # Freshly-made IAM roles sometimes aren't really ready
114
- if retries < 5
115
- sleep 10
116
- retries += 1
117
- retry
118
- end
119
- raise e
65
+ # the console does this and docs expect it to be there, so mimic the
66
+ # behavior
67
+ begin
68
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @credentials).create_log_group(
69
+ log_group_name: "/aws/lambda/#{@cloud_id}",
70
+ tags: @tags
71
+ )
72
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
120
73
  end
121
-
122
- @cloud_id = resp.function_name
123
74
  end
124
75
 
125
76
  # Called automatically by {MU::Deploy#createResources}
126
77
  def groom
127
- desc = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).get_function(
128
- function_name: @mu_name
129
- )
130
- func_arn = desc.configuration.function_arn if !desc.empty?
78
+ old_props = MU.structToHash(cloud_desc)
79
+
80
+ new_props = get_properties
81
+ code_block = new_props[:code]
82
+ new_props.reject! { |k, _v| [:code, :publish, :tags].include?(k) }
83
+ changes = {}
84
+ new_props.each_pair { |k, v|
85
+ changes[k] = v if v != old_props[k]
86
+ }
87
+ if !changes.empty?
88
+ MU.log "Updating Lambda #{@mu_name}", MU::NOTICE, details: changes
89
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).update_function_configuration(new_props)
90
+ end
91
+
92
+ if @code_sha256 and @code_sha256 != cloud_desc.code_sha_256.chomp
93
+ MU.log "Updating code in Lambda #{@mu_name}", MU::NOTICE, details: { "old" => @code_sha256, "new" => cloud_desc.code_sha_256 }
94
+ code_block[:publish] = true
95
+ code_block[:function_name] = @cloud_id
96
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).update_function_code(code_block)
97
+ end
131
98
 
132
99
  # tag_function = assign_tag(lambda_func.function_arn, @config['tags'])
133
100
 
@@ -141,7 +108,7 @@ module MU
141
108
  ### triggers must exist prior
142
109
  if @config['triggers']
143
110
  @config['triggers'].each { |tr|
144
- trigger_arn = assume_trigger_arns(tr['service'], tr['name'])
111
+ trigger_arn = resolveARN(tr['service'], tr['name'])
145
112
 
146
113
  trigger_properties = {
147
114
  action: "lambda:InvokeFunction",
@@ -151,15 +118,33 @@ module MU
151
118
  statement_id: "#{@mu_name}-ID-1",
152
119
  }
153
120
 
154
- MU.log trigger_properties, MU::DEBUG
121
+ MU.log "Adding #{tr['service']} #{tr['name']} trigger to Lambda function #{@cloud_id}", details: trigger_properties
155
122
  begin
156
123
  MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger_properties)
157
124
  rescue Aws::Lambda::Errors::ResourceConflictException
125
+ # just means the permission is already there
158
126
  end
159
- adjust_trigger(tr['service'], trigger_arn, func_arn, @mu_name)
127
+ adjust_trigger(tr['service'], trigger_arn, arn, @mu_name)
160
128
  }
161
129
 
162
130
  end
131
+
132
+ if @config['invoke_on_completion']
133
+ invoke_params = {
134
+ function_name: @cloud_id,
135
+ invocation_type: @config['invoke_on_completion']['invocation_type'],
136
+ log_type: "Tail"
137
+ }
138
+ if @config['invoke_on_completion']['payload']
139
+ invoke_params[:payload] = JSON.generate(@config['invoke_on_completion']['payload'])
140
+ end
141
+ resp = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).invoke(invoke_params)
142
+ if resp.status_code == 200
143
+ MU.log "Invoked #{@cloud_id}", MU::NOTICE, details: Base64.decode64(resp.log_result)
144
+ else
145
+ MU.log "Invoked #{@cloud_id} and got #{resp.status_code} (#{resp.function_error})", MU::WARN, details: Base64.decode64(resp.log_result)
146
+ end
147
+ end
163
148
  end
164
149
 
165
150
  # Intended to be called by other Mu resources, such as Endpoints (API
@@ -170,13 +155,16 @@ module MU
170
155
  function_name: @mu_name,
171
156
  principal: "#{calling_service}.amazonaws.com",
172
157
  source_arn: calling_arn,
173
- statement_id: "#{calling_service}-#{calling_name}",
158
+ statement_id: "#{calling_service}-#{calling_name.gsub(/[^a-z0-9\-_]/i, '_')}",
174
159
  }
175
160
 
176
161
  begin
177
162
  # XXX There doesn't seem to be an API call to list or view existing
178
163
  # permissions, wtaf. This means we can't intelligently guard this.
179
164
  MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger)
165
+ rescue Aws::Lambda::Errors::ValidationException => e
166
+ MU.log e.message+" (calling_arn: #{calling_arn}, calling_service: #{calling_service}, calling_name: #{calling_name})", MU::ERR, details: trigger
167
+ raise e
180
168
  rescue Aws::Lambda::Errors::ResourceConflictException => e
181
169
  if e.message.match(/already exists/)
182
170
  MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).remove_permission(
@@ -192,17 +180,23 @@ module MU
192
180
  end
193
181
 
194
182
  # Look up an ARN for a given trigger type and resource name
195
- def assume_trigger_arns(svc, name)
196
- supported_triggers = %w(apigateway sns events event cloudwatch_event)
183
+ def resolveARN(svc, name)
184
+ supported_triggers = %w(apigateway sns events event cloudwatch_event dynamodb)
197
185
  if supported_triggers.include?(svc.downcase)
198
186
  arn = nil
199
187
  case svc.downcase
200
188
  when 'sns'
201
- arn = "arn:aws:sns:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
189
+ sib_sns = @deploy.findLitterMate(name: name, type: "notifiers")
190
+ arn = sib_sns ? sib_sns.arn : "arn:aws:sns:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
202
191
  when 'alarm','events', 'event', 'cloudwatch_event'
203
- arn = "arn:aws:events:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:rule/#{name}"
192
+ sib_event = @deploy.findLitterMate(name: name, type: "job")
193
+ arn = sib_event ? sib_event.arn : "arn:aws:events:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:rule/#{name}"
194
+ when 'dynamodb'
195
+ sib_dynamo = @deploy.findLitterMate(name: name, type: "nosqldb")
196
+ arn = sib_dynamo ? sib_dynamo.arn : "arn:aws:dynamodb:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:table/#{name}"
204
197
  when 'apigateway'
205
- arn = "arn:aws:apigateway:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
198
+ sib_apig = @deploy.findLitterMate(name: name, type: "endpoints")
199
+ arn = sib_apig ? sib_apig.arn : "arn:aws:apigateway:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
206
200
  when 's3'
207
201
  arn = ''
208
202
  end
@@ -219,13 +213,21 @@ module MU
219
213
  case trig_type
220
214
 
221
215
  when 'sns'
222
- # XXX don't do this, use MU::Cloud::AWS::Notification
223
- sns_client = MU::Cloud::AWS.sns(region: region, credentials: @config['credentials'])
224
- sns_client.subscribe({
225
- topic_arn: trig_arn,
226
- protocol: protocol,
227
- endpoint: func_arn
228
- })
216
+ MU::Cloud.resourceClass("AWS", "Notifier").subscribe(trig_arn, arn, "lambda", region: @config['region'], credentials: @credentials)
217
+ when 'dynamodb'
218
+ stream = MU::Cloud::AWS.dynamostream(region: @config['region'], credentials: @config['credentials']).list_streams(table_name: trig_arn.sub(/.*?:table\//, '')).streams.first
219
+ # XXX guard this
220
+ MU.log "Adding DynamoDB Stream from #{stream.stream_arn} as trigger for #{@cloud_id}"
221
+ begin
222
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).create_event_source_mapping(
223
+ event_source_arn: stream.stream_arn,
224
+ function_name: @cloud_id,
225
+ starting_position: "TRIM_HORIZON" # ...whatever that is
226
+ )
227
+ rescue ::Aws::Lambda::Errors::ResourceConflictException
228
+ end
229
+
230
+ # MU::Cloud.resourceClass("AWS", "NoSQLDB").subscribe(trig_arn, arn, "lambda", region: @config['region'], credentials: @credentials)
229
231
  when 'event','cloudwatch_event', 'events'
230
232
  # XXX don't do this, use MU::Cloud::AWS::Log
231
233
  MU::Cloud::AWS.cloudwatch_events(region: region, credentials: @config['credentials']).put_targets({
@@ -237,9 +239,8 @@ module MU
237
239
  }
238
240
  ]
239
241
  })
240
- # when 'apigateway'
241
- # XXX this is actually happening in ::Endpoint... maybe...
242
- # MU.log "Creation of API Gateway integrations not yet implemented, you'll have to do this manually", MU::WARN, details: "(because we'll basically have to implement all of APIG for this)"
242
+ when 'apigateway'
243
+ addTrigger(trig_arn, "lambda", trig_arn.sub(/.*?([a-z0-9\-_]+)$/i, '\1'))
243
244
  end
244
245
  end
245
246
 
@@ -247,9 +248,8 @@ module MU
247
248
  # Return the metadata for this Function rule
248
249
  # @return [Hash]
249
250
  def notify
250
- deploy_struct = MU.structToHash(MU::Cloud::AWS::Function.find(cloud_id: @cloud_id, credentials: @config['credentials'], region: @config['region']).values.first)
251
- deploy_struct['mu_name'] = @mu_name
252
- return deploy_struct
251
+ return nil if !cloud_desc
252
+ MU.structToHash(cloud_desc, stringify_keys: true)
253
253
  end
254
254
 
255
255
  # Does this resource type exist as a global (cloud-wide) artifact, or
@@ -270,14 +270,14 @@ module MU
270
270
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
271
271
  # @param region [String]: The cloud provider region
272
272
  # @return [void]
273
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
273
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
274
274
  MU.log "AWS::Function.cleanup: need to support flags['known']", MU::DEBUG, details: flags
275
275
 
276
276
  MU::Cloud::AWS.lambda(credentials: credentials, region: region).list_functions.functions.each { |f|
277
277
  desc = MU::Cloud::AWS.lambda(credentials: credentials, region: region).get_function(
278
278
  function_name: f.function_name
279
279
  )
280
- if desc.tags and desc.tags["MU-ID"] == MU.deploy_id and (desc.tags["MU-MASTER-IP"] == MU.mu_public_ip or ignoremaster)
280
+ if desc.tags and desc.tags["MU-ID"] == deploy_id and (desc.tags["MU-MASTER-IP"] == MU.mu_public_ip or ignoremaster)
281
281
  MU.log "Deleting Lambda function #{f.function_name}"
282
282
  if !noop
283
283
  MU::Cloud::AWS.lambda(credentials: credentials, region: region).delete_function(
@@ -292,7 +292,7 @@ module MU
292
292
  # Canonical Amazon Resource Number for this resource
293
293
  # @return [String]
294
294
  def arn
295
- cloud_desc.function_arn
295
+ cloud_desc ? cloud_desc.function_arn : nil
296
296
  end
297
297
 
298
298
  # Locate an existing function.
@@ -334,6 +334,20 @@ module MU
334
334
  bok['timeout'] = cloud_desc.timeout
335
335
 
336
336
  function = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_function(function_name: bok['name'])
337
+ # event_srcs = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).list_event_source_mappings(function_name: @cloud_id)
338
+ # if event_srcs and !event_srcs.event_source_mappings.empty?
339
+ # MU.log "dem mappings tho #{@cloud_id}", MU::WARN, details: event_srcs
340
+ # end
341
+
342
+ # begin
343
+ # invoke_cfg = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_function_event_invoke_config(function_name: @cloud_id)
344
+ # MU.log "invoke config #{@cloud_id}", MU::WARN, details: invoke_cfg
345
+ # rescue ::Aws::Lambda::Errors::ResourceNotFoundException
346
+ # end
347
+
348
+ # MU.log @cloud_id, MU::WARN, details: cloud_desc if @cloud_id == "Espier-Scheduled-Scanner"
349
+ # MU.log "configuration #{@cloud_id}", MU::WARN, details: MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_function_configuration(function_name: @cloud_id) if @cloud_id == "Espier-Scheduled-Scanner"
350
+
337
351
 
338
352
  if function.code.repository_type == "S3"
339
353
  bok['code'] = {}
@@ -393,16 +407,29 @@ module MU
393
407
 
394
408
  if function.configuration.role
395
409
  shortname = function.configuration.role.sub(/.*?role\/([^\/]+)$/, '\1')
396
- MU.log shortname, MU::NOTICE, details: function.configuration.role
397
410
  bok['role'] = MU::Config::Ref.get(
398
411
  id: shortname,
399
- name: shortname,
400
412
  cloud: "AWS",
401
413
  type: "roles"
402
414
  )
403
415
  end
416
+
417
+ begin
418
+ pol = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_policy(function_name: @cloud_id).policy
419
+ MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV-2020080900-LN-ON-DEMAND-SCANNER"
420
+ if pol
421
+ bok['triggers'] ||= []
422
+ JSON.parse(pol)["Statement"].each { |s|
423
+ bok['triggers'] << {
424
+ "service" => s["Principal"]["Service"].sub(/\..*/, ''),
425
+ "name" => s["Resource"].sub(/.*?[:\/]([^:\/]+)$/, '\1')
426
+ }
427
+ }
428
+ end
429
+ rescue ::Aws::Lambda::Errors::ResourceNotFoundException
430
+ end
404
431
  #MU.log @cloud_id, MU::NOTICE, details: function
405
- # XXX triggers, permissions
432
+ # XXX permissions
406
433
 
407
434
  bok
408
435
  end
@@ -414,6 +441,22 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
414
441
  def self.schema(_config)
415
442
  toplevel_required = ["runtime"]
416
443
  schema = {
444
+ "invoke_on_completion" => {
445
+ "type" => "object",
446
+ "description" => "Setting this will cause this Lambda function to be invoked when its groom phase is complete.",
447
+ "required" => ["invocation_type"],
448
+ "properties" => {
449
+ "invocation_type" => {
450
+ "type" => "string",
451
+ "enum" => ["RequestResponse", "Event", "Dryrun"],
452
+ "default" => "RequestReponse"
453
+ },
454
+ "payload" => {
455
+ "type" => "object",
456
+ "description" => "Optional input to the function, which will be formatted as JSON and sent for execution"
457
+ }
458
+ }
459
+ },
417
460
  "triggers" => {
418
461
  "type" => "array",
419
462
  "items" => {
@@ -423,7 +466,7 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
423
466
  "properties" => {
424
467
  "service" => {
425
468
  "type" => "string",
426
- "enum" => %w{apigateway events s3 sns sqs dynamodb kinesis ses cognito alexa iot},
469
+ "enum" => %w{apigateway events s3 sns sqs dynamodb kinesis ses cognito alexa iot lex},
427
470
  "description" => "The name of the AWS service that will trigger this function"
428
471
  },
429
472
  "name" => {
@@ -482,6 +525,28 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
482
525
  def self.validateConfig(function, configurator)
483
526
  ok = true
484
527
 
528
+ if function['triggers']
529
+ function['triggers'].each { |t|
530
+ mu_type = if t["service"] == "sns"
531
+ "notifiers"
532
+ elsif t["service"] == "apigateway"
533
+ "endpoints"
534
+ elsif t["service"] == "s3"
535
+ "buckets"
536
+ elsif t["service"] == "dynamodb"
537
+ "nosqldbs"
538
+ elsif t["service"] == "events"
539
+ "jobs"
540
+ elsif t["service"] == "sqs"
541
+ "msg_queues"
542
+ end
543
+
544
+ if mu_type
545
+ MU::Config.addDependency(function, t['name'], mu_type, no_create_wait: true)
546
+ end
547
+ }
548
+ end
549
+
485
550
  if function['vpc']
486
551
  fwname = "lambda-#{function['name']}"
487
552
  # default to allowing pings, if no ingress_rules were specified
@@ -505,14 +570,13 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
505
570
  function["add_firewall_rules"] << {"name" => fwname}
506
571
  function["permissions"] ||= []
507
572
  function["permissions"] << "network"
508
- function['dependencies'] ||= []
509
- function['dependencies'] << {
510
- "name" => fwname,
511
- "type" => "firewall_rule"
512
- }
573
+ MU::Config.addDependency(function, fwname, "firewall_rule")
513
574
  end
514
575
 
515
- if !function['iam_role']
576
+ function['role'] ||= function['iam_role']
577
+ function.delete("iam_role")
578
+
579
+ if !function['role']
516
580
  policy_map = {
517
581
  "basic" => "AWSLambdaBasicExecutionRole",
518
582
  "kinesis" => "AWSLambdaKinesisExecutionRole",
@@ -541,13 +605,21 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
541
605
  }
542
606
  configurator.insertKitten(roledesc, "roles")
543
607
 
544
- function['dependencies'] ||= []
545
- function['iam_role'] = function['name']+"execrole"
608
+ function['role'] = function['name']+"execrole"
546
609
 
547
- function['dependencies'] << {
548
- "type" => "role",
549
- "name" => function['name']+"execrole"
550
- }
610
+ end
611
+
612
+ if function['role'].is_a?(String)
613
+ function['role'] = MU::Config::Ref.get(
614
+ name: function['role'],
615
+ type: "roles",
616
+ cloud: "AWS",
617
+ credentials: function['credentials']
618
+ )
619
+ end
620
+
621
+ if function['role']['name']
622
+ MU::Config.addDependency(function, function['role']['name'], "role")
551
623
  end
552
624
 
553
625
  ok
@@ -555,23 +627,109 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
555
627
 
556
628
  private
557
629
 
558
- # Given an IAM role name, resolve to ARN. Will attempt to identify any
559
- # sibling Mu role resources by this name first, and failing that, will
560
- # do a plain get_role() to the IAM API for the provided name.
561
- # @param name [String]
562
- def get_role_arn(name)
563
- sib_role = @deploy.findLitterMate(name: name, type: "roles")
564
- return sib_role.cloudobj.arn if sib_role
630
+ def get_properties
631
+ role_obj = MU::Config::Ref.get(@config['role']).kitten(@deploy, cloud: "AWS")
632
+ raise MuError.new "Failed to fetch object from role reference", details: @config['role'].to_h if !role_obj
565
633
 
566
- begin
567
- role = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_role({
568
- role_name: name.to_s
569
- })
570
- return role['role']['arn']
571
- rescue StandardError => e
572
- MU.log "#{e}", MU::ERR
634
+ lambda_properties = {
635
+ code: {},
636
+ function_name: @mu_name,
637
+ handler: @config['handler'],
638
+ publish: true,
639
+ role: role_obj.arn,
640
+ runtime: @config['runtime'],
641
+ }
642
+
643
+ if @config['code']['zip_file'] or @config['code']['path']
644
+ tempfile = nil
645
+ if @config['code']['path']
646
+ tempfile = Tempfile.new
647
+ MU.log "#{@mu_name} using code at #{@config['code']['path']}"
648
+ MU::Master.zipDir(@config['code']['path'], tempfile.path)
649
+ @config['code']['zip_file'] = tempfile.path
650
+ else
651
+ MU.log "#{@mu_name} using code packaged at #{@config['code']['zip_file']}"
652
+ end
653
+ zip = File.read(@config['code']['zip_file'])
654
+ @code_sha256 = Base64.encode64(Digest::SHA256.digest(zip)).chomp
655
+ lambda_properties[:code][:zip_file] = zip
656
+ if tempfile
657
+ tempfile.close
658
+ tempfile.unlink
659
+ end
660
+ else
661
+ lambda_properties[:code][:s3_bucket] = @config['code']['s3_bucket']
662
+ lambda_properties[:code][:s3_key] = @config['code']['s3_key']
663
+ if @config['code']['s3_object_version']
664
+ lambda_properties[:code][:s3_object_version] = @config['code']['s3_object_version']
665
+ end
666
+ # XXX need to download to a temporarily file, read it in, and calculate the digest in order to trigger updates in groom
667
+ end
668
+
669
+ if @config.has_key?('timeout')
670
+ lambda_properties[:timeout] = @config['timeout'].to_i ## secs
671
+ end
672
+
673
+ if @config.has_key?('memory')
674
+ lambda_properties[:memory_size] = @config['memory'].to_i
675
+ end
676
+
677
+ SIBLING_VARS.each_key { |sib_type|
678
+ siblings = @deploy.findLitterMate(return_all: true, type: sib_type, cloud: "AWS")
679
+ if siblings
680
+ siblings.each_value { |sibling|
681
+ metadata = sibling.notify
682
+ if !metadata
683
+ MU.log "Failed to extract metadata from sibling #{sibling}", MU::WARN
684
+ next
685
+ end
686
+ SIBLING_VARS[sib_type].each { |var|
687
+ if metadata[var]
688
+ @config['environment_variables'] ||= []
689
+ @config['environment_variables'] << {
690
+ "key" => (sibling.config['name']+"_"+var).gsub(/[^a-z0-9_]/i, '_'),
691
+ "value" => metadata[var]
692
+ }
693
+ end
694
+ }
695
+ }
696
+ end
697
+ }
698
+
699
+ if @config.has_key?('environment_variables')
700
+ lambda_properties[:environment] = {
701
+ variables: Hash[@config['environment_variables'].map { |v| [v['key'], v['value']] }]
702
+ }
703
+ end
704
+
705
+ lambda_properties[:tags] = {}
706
+ MU::MommaCat.listStandardTags.each_pair { |k, v|
707
+ lambda_properties[:tags][k] = v
708
+ }
709
+ if @config['tags']
710
+ @config['tags'].each { |tag|
711
+ lambda_properties[:tags][tag['key']] = tag['value']
712
+ }
573
713
  end
574
- nil
714
+
715
+ if @config.has_key?('vpc')
716
+ sgs = []
717
+ if @config['add_firewall_rules']
718
+ @config['add_firewall_rules'].each { |sg|
719
+ sg = @deploy.findLitterMate(type: "firewall_rule", name: sg['name'])
720
+ sgs << sg.cloud_id if sg and sg.cloud_id
721
+ }
722
+ end
723
+ if !@vpc
724
+ raise MuError, "Function #{@config['name']} had a VPC configured, but none was loaded"
725
+ end
726
+ lambda_properties[:vpc_config] = {
727
+ :subnet_ids => @vpc.subnets.map { |s| s.cloud_id },
728
+ :security_group_ids => sgs
729
+ }
730
+ end
731
+
732
+ lambda_properties
575
733
  end
576
734
 
577
735
  end