cloud-mu 3.1.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +15 -3
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +21 -13
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +4 -4
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +147 -37
  23. data/cloud-mu.gemspec +22 -20
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +3 -2
  27. data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
  28. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  29. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  30. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  31. data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
  32. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  33. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  34. data/cookbooks/mu-tools/resources/disk.rb +1 -1
  35. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  36. data/extras/clean-stock-amis +25 -19
  37. data/extras/generate-stock-images +1 -0
  38. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  39. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  40. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  41. data/extras/image-generators/Google/centos6.yaml +1 -0
  42. data/extras/image-generators/Google/centos7.yaml +1 -1
  43. data/modules/mommacat.ru +6 -16
  44. data/modules/mu.rb +158 -111
  45. data/modules/mu/adoption.rb +404 -71
  46. data/modules/mu/cleanup.rb +221 -306
  47. data/modules/mu/cloud.rb +129 -1633
  48. data/modules/mu/cloud/database.rb +49 -0
  49. data/modules/mu/cloud/dnszone.rb +44 -0
  50. data/modules/mu/cloud/machine_images.rb +212 -0
  51. data/modules/mu/cloud/providers.rb +81 -0
  52. data/modules/mu/cloud/resource_base.rb +926 -0
  53. data/modules/mu/cloud/server.rb +40 -0
  54. data/modules/mu/cloud/server_pool.rb +1 -0
  55. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  56. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  57. data/modules/mu/cloud/wrappers.rb +169 -0
  58. data/modules/mu/config.rb +171 -1767
  59. data/modules/mu/config/alarm.rb +2 -6
  60. data/modules/mu/config/bucket.rb +32 -3
  61. data/modules/mu/config/cache_cluster.rb +2 -2
  62. data/modules/mu/config/cdn.rb +100 -0
  63. data/modules/mu/config/collection.rb +4 -4
  64. data/modules/mu/config/container_cluster.rb +9 -4
  65. data/modules/mu/config/database.rb +84 -105
  66. data/modules/mu/config/database.yml +1 -2
  67. data/modules/mu/config/dnszone.rb +10 -9
  68. data/modules/mu/config/doc_helpers.rb +516 -0
  69. data/modules/mu/config/endpoint.rb +5 -4
  70. data/modules/mu/config/firewall_rule.rb +103 -4
  71. data/modules/mu/config/folder.rb +4 -4
  72. data/modules/mu/config/function.rb +19 -10
  73. data/modules/mu/config/group.rb +4 -4
  74. data/modules/mu/config/habitat.rb +4 -4
  75. data/modules/mu/config/job.rb +89 -0
  76. data/modules/mu/config/loadbalancer.rb +60 -14
  77. data/modules/mu/config/log.rb +4 -4
  78. data/modules/mu/config/msg_queue.rb +4 -4
  79. data/modules/mu/config/nosqldb.rb +4 -4
  80. data/modules/mu/config/notifier.rb +10 -21
  81. data/modules/mu/config/ref.rb +411 -0
  82. data/modules/mu/config/role.rb +4 -4
  83. data/modules/mu/config/schema_helpers.rb +509 -0
  84. data/modules/mu/config/search_domain.rb +4 -4
  85. data/modules/mu/config/server.rb +98 -71
  86. data/modules/mu/config/server.yml +1 -0
  87. data/modules/mu/config/server_pool.rb +5 -9
  88. data/modules/mu/config/storage_pool.rb +1 -1
  89. data/modules/mu/config/tail.rb +200 -0
  90. data/modules/mu/config/user.rb +4 -4
  91. data/modules/mu/config/vpc.rb +71 -27
  92. data/modules/mu/config/vpc.yml +0 -1
  93. data/modules/mu/defaults/AWS.yaml +91 -68
  94. data/modules/mu/defaults/Azure.yaml +1 -0
  95. data/modules/mu/defaults/Google.yaml +3 -2
  96. data/modules/mu/deploy.rb +43 -26
  97. data/modules/mu/groomer.rb +17 -2
  98. data/modules/mu/groomers/ansible.rb +188 -41
  99. data/modules/mu/groomers/chef.rb +116 -55
  100. data/modules/mu/logger.rb +127 -148
  101. data/modules/mu/master.rb +410 -2
  102. data/modules/mu/master/chef.rb +3 -4
  103. data/modules/mu/master/ldap.rb +3 -3
  104. data/modules/mu/master/ssl.rb +12 -3
  105. data/modules/mu/mommacat.rb +218 -2612
  106. data/modules/mu/mommacat/daemon.rb +403 -0
  107. data/modules/mu/mommacat/naming.rb +473 -0
  108. data/modules/mu/mommacat/search.rb +495 -0
  109. data/modules/mu/mommacat/storage.rb +722 -0
  110. data/modules/mu/{clouds → providers}/README.md +1 -1
  111. data/modules/mu/{clouds → providers}/aws.rb +380 -122
  112. data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
  113. data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
  114. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
  115. data/modules/mu/providers/aws/cdn.rb +782 -0
  116. data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
  117. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
  118. data/modules/mu/providers/aws/database.rb +1744 -0
  119. data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
  120. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  121. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
  122. data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
  123. data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
  124. data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
  125. data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
  126. data/modules/mu/providers/aws/job.rb +466 -0
  127. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
  128. data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
  129. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
  130. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
  131. data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
  132. data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
  133. data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
  134. data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
  135. data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
  136. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
  137. data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
  138. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  139. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  140. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  141. data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
  142. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  143. data/modules/mu/{clouds → providers}/azure.rb +29 -9
  144. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
  145. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
  146. data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
  147. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
  148. data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
  149. data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
  150. data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
  151. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  152. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  153. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  154. data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
  155. data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
  156. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  158. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  159. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  160. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  161. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  162. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  163. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  164. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  165. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  166. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
  167. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  168. data/modules/mu/{clouds → providers}/google.rb +68 -30
  169. data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
  170. data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
  171. data/modules/mu/{clouds → providers}/google/database.rb +11 -21
  172. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
  173. data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
  174. data/modules/mu/{clouds → providers}/google/function.rb +140 -168
  175. data/modules/mu/{clouds → providers}/google/group.rb +29 -34
  176. data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
  177. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
  178. data/modules/mu/{clouds → providers}/google/role.rb +94 -58
  179. data/modules/mu/{clouds → providers}/google/server.rb +243 -156
  180. data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
  181. data/modules/mu/{clouds → providers}/google/user.rb +95 -31
  182. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  183. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  184. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  185. data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
  186. data/modules/tests/aws-jobs-functions.yaml +46 -0
  187. data/modules/tests/bucket.yml +4 -0
  188. data/modules/tests/centos6.yaml +15 -0
  189. data/modules/tests/centos7.yaml +15 -0
  190. data/modules/tests/centos8.yaml +12 -0
  191. data/modules/tests/ecs.yaml +23 -0
  192. data/modules/tests/eks.yaml +1 -1
  193. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  194. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  195. data/modules/tests/includes-and-params.yaml +2 -1
  196. data/modules/tests/microservice_app.yaml +288 -0
  197. data/modules/tests/rds.yaml +108 -0
  198. data/modules/tests/regrooms/aws-iam.yaml +201 -0
  199. data/modules/tests/regrooms/bucket.yml +19 -0
  200. data/modules/tests/regrooms/rds.yaml +123 -0
  201. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  202. data/modules/tests/super_complex_bok.yml +2 -2
  203. data/modules/tests/super_simple_bok.yml +3 -5
  204. data/modules/tests/win2k12.yaml +17 -5
  205. data/modules/tests/win2k16.yaml +25 -0
  206. data/modules/tests/win2k19.yaml +25 -0
  207. data/requirements.txt +1 -0
  208. data/spec/mu/clouds/azure_spec.rb +2 -2
  209. metadata +240 -154
  210. data/extras/image-generators/AWS/windows.yaml +0 -18
  211. data/modules/mu/clouds/aws/database.rb +0 -1985
  212. data/modules/mu/clouds/aws/endpoint.rb +0 -592
@@ -15,7 +15,7 @@
15
15
  module MU
16
16
  class Cloud
17
17
  class AWS
18
- # A log as configured in {MU::Config::BasketofKittens::logs}
18
+ # A log as configured in {MU::Config::BasketofKittens::folders}
19
19
  class Folder < MU::Cloud::Folder
20
20
 
21
21
  # 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.
@@ -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.
@@ -78,20 +78,20 @@ module MU
78
78
  end
79
79
 
80
80
  # Cloud-specific configuration properties.
81
- # @param config [MU::Config]: The calling MU::Config object
81
+ # @param _config [MU::Config]: The calling MU::Config object
82
82
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
83
- def self.schema(config)
83
+ def self.schema(_config)
84
84
  toplevel_required = []
85
85
  schema = {
86
86
  }
87
87
  [toplevel_required, schema]
88
88
  end
89
89
 
90
- # Cloud-specific pre-processing of {MU::Config::BasketofKittens::logs}, bare and unvalidated.
91
- # @param log [Hash]: The resource to process and validate
92
- # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
90
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::folders}, bare and unvalidated.
91
+ # @param _folder [Hash]: The resource to process and validate
92
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
93
93
  # @return [Boolean]: True if validation succeeded, False otherwise
94
- def self.validateConfig(log, configurator)
94
+ def self.validateConfig(_folder, _configurator)
95
95
  ok = true
96
96
 
97
97
  ok
@@ -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)
@@ -29,12 +41,12 @@ module MU
29
41
  def assign_tag(resource_arn, tag_list, region=@config['region'])
30
42
  begin
31
43
  tag_list.each do |each_pair|
32
- tag_resp = MU::Cloud::AWS.lambda(region: region, credentials: @config['credentials']).tag_resource({
44
+ MU::Cloud::AWS.lambda(region: region, credentials: @config['credentials']).tag_resource({
33
45
  resource: resource_arn,
34
46
  tags: each_pair
35
47
  })
36
48
  end
37
- rescue Exception => e
49
+ rescue StandardError => e
38
50
  MU.log e, MU::ERR
39
51
  end
40
52
  end
@@ -42,92 +54,44 @@ 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: @config['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
-
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
120
- end
121
64
 
122
- @cloud_id = resp.function_name
65
+ # the console does this and docs expect it to be there, so mimic the
66
+ # behavior
67
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @credentials).create_log_group(
68
+ log_group_name: "/aws/lambda/#{@cloud_id}",
69
+ tags: @tags
70
+ )
123
71
  end
124
72
 
125
73
  # Called automatically by {MU::Deploy#createResources}
126
74
  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?
75
+ old_props = MU.structToHash(cloud_desc)
76
+
77
+ new_props = get_properties
78
+ code_block = new_props[:code]
79
+ new_props.reject! { |k, _v| [:code, :publish, :tags].include?(k) }
80
+ changes = {}
81
+ new_props.each_pair { |k, v|
82
+ changes[k] = v if v != old_props[k]
83
+ }
84
+ if !changes.empty?
85
+ MU.log "Updating Lambda #{@mu_name}", MU::NOTICE, details: changes
86
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).update_function_configuration(new_props)
87
+ end
88
+
89
+ if @code_sha256 and @code_sha256 != cloud_desc.code_sha_256.chomp
90
+ MU.log "Updating code in Lambda #{@mu_name}", MU::NOTICE, details: { "old" => @code_sha256, "new" => cloud_desc.code_sha_256 }
91
+ code_block[:publish] = true
92
+ code_block[:function_name] = @cloud_id
93
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).update_function_code(code_block)
94
+ end
131
95
 
132
96
  # tag_function = assign_tag(lambda_func.function_arn, @config['tags'])
133
97
 
@@ -141,7 +105,7 @@ module MU
141
105
  ### triggers must exist prior
142
106
  if @config['triggers']
143
107
  @config['triggers'].each { |tr|
144
- trigger_arn = assume_trigger_arns(tr['service'], tr['name'])
108
+ trigger_arn = resolveARN(tr['service'], tr['name'])
145
109
 
146
110
  trigger_properties = {
147
111
  action: "lambda:InvokeFunction",
@@ -151,15 +115,33 @@ module MU
151
115
  statement_id: "#{@mu_name}-ID-1",
152
116
  }
153
117
 
154
- MU.log trigger_properties, MU::DEBUG
118
+ MU.log "Adding #{tr['service']} #{tr['name']} trigger to Lambda function #{@cloud_id}", details: trigger_properties
155
119
  begin
156
- add_trigger = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger_properties)
120
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger_properties)
157
121
  rescue Aws::Lambda::Errors::ResourceConflictException
122
+ # just means the permission is already there
158
123
  end
159
- adjust_trigger(tr['service'], trigger_arn, func_arn, @mu_name)
124
+ adjust_trigger(tr['service'], trigger_arn, arn, @mu_name)
160
125
  }
161
126
 
162
127
  end
128
+
129
+ if @config['invoke_on_completion']
130
+ invoke_params = {
131
+ function_name: @cloud_id,
132
+ invocation_type: @config['invoke_on_completion']['invocation_type'],
133
+ log_type: "Tail"
134
+ }
135
+ if @config['invoke_on_completion']['payload']
136
+ invoke_params[:payload] = JSON.generate(@config['invoke_on_completion']['payload'])
137
+ end
138
+ resp = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).invoke(invoke_params)
139
+ if resp.status_code == 200
140
+ MU.log "Invoked #{@cloud_id}", MU::NOTICE, details: Base64.decode64(resp.log_result)
141
+ else
142
+ MU.log "Invoked #{@cloud_id} and got #{resp.status_code} (#{resp.function_error})", MU::WARN, details: Base64.decode64(resp.log_result)
143
+ end
144
+ end
163
145
  end
164
146
 
165
147
  # Intended to be called by other Mu resources, such as Endpoints (API
@@ -170,13 +152,16 @@ module MU
170
152
  function_name: @mu_name,
171
153
  principal: "#{calling_service}.amazonaws.com",
172
154
  source_arn: calling_arn,
173
- statement_id: "#{calling_service}-#{calling_name}",
155
+ statement_id: "#{calling_service}-#{calling_name.gsub(/[^a-z0-9\-_]/i, '_')}",
174
156
  }
175
157
 
176
158
  begin
177
159
  # XXX There doesn't seem to be an API call to list or view existing
178
160
  # permissions, wtaf. This means we can't intelligently guard this.
179
- add_trigger = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger)
161
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).add_permission(trigger)
162
+ rescue Aws::Lambda::Errors::ValidationException => e
163
+ MU.log e.message+" (calling_arn: #{calling_arn}, calling_service: #{calling_service}, calling_name: #{calling_name})", MU::ERR, details: trigger
164
+ raise e
180
165
  rescue Aws::Lambda::Errors::ResourceConflictException => e
181
166
  if e.message.match(/already exists/)
182
167
  MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).remove_permission(
@@ -192,17 +177,23 @@ module MU
192
177
  end
193
178
 
194
179
  # 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)
180
+ def resolveARN(svc, name)
181
+ supported_triggers = %w(apigateway sns events event cloudwatch_event dynamodb)
197
182
  if supported_triggers.include?(svc.downcase)
198
183
  arn = nil
199
184
  case svc.downcase
200
185
  when 'sns'
201
- arn = "arn:aws:sns:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
186
+ sib_sns = @deploy.findLitterMate(name: name, type: "notifiers")
187
+ arn = sib_sns ? sib_sns.arn : "arn:aws:sns:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
202
188
  when 'alarm','events', 'event', 'cloudwatch_event'
203
- arn = "arn:aws:events:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:rule/#{name}"
189
+ sib_event = @deploy.findLitterMate(name: name, type: "job")
190
+ arn = sib_event ? sib_event.arn : "arn:aws:events:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:rule/#{name}"
191
+ when 'dynamodb'
192
+ sib_dynamo = @deploy.findLitterMate(name: name, type: "nosqldb")
193
+ arn = sib_dynamo ? sib_dynamo.arn : "arn:aws:dynamodb:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:table/#{name}"
204
194
  when 'apigateway'
205
- arn = "arn:aws:apigateway:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
195
+ sib_apig = @deploy.findLitterMate(name: name, type: "endpoints")
196
+ arn = sib_apig ? sib_apig.arn : "arn:aws:apigateway:#{@config['region']}:#{MU::Cloud::AWS.credToAcct(@config['credentials'])}:#{name}"
206
197
  when 's3'
207
198
  arn = ''
208
199
  end
@@ -219,16 +210,24 @@ module MU
219
210
  case trig_type
220
211
 
221
212
  when 'sns'
222
- # XXX don't do this, use MU::Cloud::AWS::Notification
223
- sns_client = MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials'])
224
- sub_to_what = sns_client.subscribe({
225
- topic_arn: trig_arn,
226
- protocol: protocol,
227
- endpoint: func_arn
228
- })
213
+ MU::Cloud.resourceClass("AWS", "Notifier").subscribe(trig_arn, arn, "lambda", region: @config['region'], credentials: @credentials)
214
+ when 'dynamodb'
215
+ stream = MU::Cloud::AWS.dynamostream(region: @config['region'], credentials: @config['credentials']).list_streams(table_name: trig_arn.sub(/.*?:table\//, '')).streams.first
216
+ # XXX guard this
217
+ MU.log "Adding DynamoDB Stream from #{stream.stream_arn} as trigger for #{@cloud_id}"
218
+ begin
219
+ MU::Cloud::AWS.lambda(region: @config['region'], credentials: @config['credentials']).create_event_source_mapping(
220
+ event_source_arn: stream.stream_arn,
221
+ function_name: @cloud_id,
222
+ starting_position: "TRIM_HORIZON" # ...whatever that is
223
+ )
224
+ rescue ::Aws::Lambda::Errors::ResourceConflictException
225
+ end
226
+
227
+ # MU::Cloud.resourceClass("AWS", "NoSQLDB").subscribe(trig_arn, arn, "lambda", region: @config['region'], credentials: @credentials)
229
228
  when 'event','cloudwatch_event', 'events'
230
229
  # XXX don't do this, use MU::Cloud::AWS::Log
231
- client = MU::Cloud::AWS.cloudwatch_events(region: @config['region'], credentials: @config['credentials']).put_targets({
230
+ MU::Cloud::AWS.cloudwatch_events(region: region, credentials: @config['credentials']).put_targets({
232
231
  rule: @config['trigger']['name'],
233
232
  targets: [
234
233
  {
@@ -237,9 +236,8 @@ module MU
237
236
  }
238
237
  ]
239
238
  })
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)"
239
+ when 'apigateway'
240
+ addTrigger(trig_arn, "lambda", trig_arn.sub(/.*?([a-z0-9\-_]+)$/i, '\1'))
243
241
  end
244
242
  end
245
243
 
@@ -247,9 +245,8 @@ module MU
247
245
  # Return the metadata for this Function rule
248
246
  # @return [Hash]
249
247
  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
248
+ return nil if !cloud_desc
249
+ MU.structToHash(cloud_desc, stringify_keys: true)
253
250
  end
254
251
 
255
252
  # Does this resource type exist as a global (cloud-wide) artifact, or
@@ -270,12 +267,14 @@ module MU
270
267
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
271
268
  # @param region [String]: The cloud provider region
272
269
  # @return [void]
273
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
270
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
271
+ MU.log "AWS::Function.cleanup: need to support flags['known']", MU::DEBUG, details: flags
272
+
274
273
  MU::Cloud::AWS.lambda(credentials: credentials, region: region).list_functions.functions.each { |f|
275
274
  desc = MU::Cloud::AWS.lambda(credentials: credentials, region: region).get_function(
276
275
  function_name: f.function_name
277
276
  )
278
- if desc.tags and desc.tags["MU-ID"] == MU.deploy_id
277
+ if desc.tags and desc.tags["MU-ID"] == deploy_id and (desc.tags["MU-MASTER-IP"] == MU.mu_public_ip or ignoremaster)
279
278
  MU.log "Deleting Lambda function #{f.function_name}"
280
279
  if !noop
281
280
  MU::Cloud::AWS.lambda(credentials: credentials, region: region).delete_function(
@@ -290,7 +289,7 @@ module MU
290
289
  # Canonical Amazon Resource Number for this resource
291
290
  # @return [String]
292
291
  def arn
293
- cloud_desc.function_arn
292
+ cloud_desc ? cloud_desc.function_arn : nil
294
293
  end
295
294
 
296
295
  # Locate an existing function.
@@ -312,7 +311,7 @@ module MU
312
311
  # Reverse-map our cloud description into a runnable config hash.
313
312
  # We assume that any values we have in +@config+ are placeholders, and
314
313
  # calculate our own accordingly based on what's live in the cloud.
315
- def toKitten(rootparent: nil, billing: nil, habitats: nil)
314
+ def toKitten(**_args)
316
315
  bok = {
317
316
  "cloud" => "AWS",
318
317
  "credentials" => @config['credentials'],
@@ -332,6 +331,20 @@ module MU
332
331
  bok['timeout'] = cloud_desc.timeout
333
332
 
334
333
  function = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_function(function_name: bok['name'])
334
+ # event_srcs = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).list_event_source_mappings(function_name: @cloud_id)
335
+ # if event_srcs and !event_srcs.event_source_mappings.empty?
336
+ # MU.log "dem mappings tho #{@cloud_id}", MU::WARN, details: event_srcs
337
+ # end
338
+
339
+ # begin
340
+ # invoke_cfg = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_function_event_invoke_config(function_name: @cloud_id)
341
+ # MU.log "invoke config #{@cloud_id}", MU::WARN, details: invoke_cfg
342
+ # rescue ::Aws::Lambda::Errors::ResourceNotFoundException
343
+ # end
344
+
345
+ # MU.log @cloud_id, MU::WARN, details: cloud_desc if @cloud_id == "Espier-Scheduled-Scanner"
346
+ # 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"
347
+
335
348
 
336
349
  if function.code.repository_type == "S3"
337
350
  bok['code'] = {}
@@ -391,27 +404,56 @@ module MU
391
404
 
392
405
  if function.configuration.role
393
406
  shortname = function.configuration.role.sub(/.*?role\/([^\/]+)$/, '\1')
394
- MU.log shortname, MU::NOTICE, details: function.configuration.role
395
407
  bok['role'] = MU::Config::Ref.get(
396
408
  id: shortname,
397
- name: shortname,
398
409
  cloud: "AWS",
399
410
  type: "roles"
400
411
  )
401
412
  end
413
+
414
+ begin
415
+ pol = MU::Cloud::AWS.lambda(region: @config['region'], credentials: @credentials).get_policy(function_name: @cloud_id).policy
416
+ MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV-2020080900-LN-ON-DEMAND-SCANNER"
417
+ if pol
418
+ bok['triggers'] ||= []
419
+ JSON.parse(pol)["Statement"].each { |s|
420
+ bok['triggers'] << {
421
+ "service" => s["Principal"]["Service"].sub(/\..*/, ''),
422
+ "name" => s["Resource"].sub(/.*?[:\/]([^:\/]+)$/, '\1')
423
+ }
424
+ }
425
+ end
426
+ rescue ::Aws::Lambda::Errors::ResourceNotFoundException
427
+ end
402
428
  #MU.log @cloud_id, MU::NOTICE, details: function
403
- # XXX triggers, permissions
429
+ # XXX permissions
404
430
 
405
431
  bok
406
432
  end
407
433
 
408
434
 
409
435
  # Cloud-specific configuration properties.
410
- # @param config [MU::Config]: The calling MU::Config object
436
+ # @param _config [MU::Config]: The calling MU::Config object
411
437
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
412
- def self.schema(config)
438
+ def self.schema(_config)
413
439
  toplevel_required = ["runtime"]
414
440
  schema = {
441
+ "invoke_on_completion" => {
442
+ "type" => "object",
443
+ "description" => "Setting this will cause this Lambda function to be invoked when its groom phase is complete.",
444
+ "required" => ["invocation_type"],
445
+ "properties" => {
446
+ "invocation_type" => {
447
+ "type" => "string",
448
+ "enum" => ["RequestResponse", "Event", "Dryrun"],
449
+ "default" => "RequestReponse"
450
+ },
451
+ "payload" => {
452
+ "type" => "object",
453
+ "description" => "Optional input to the function, which will be formatted as JSON and sent for execution"
454
+ }
455
+ }
456
+ },
415
457
  "triggers" => {
416
458
  "type" => "array",
417
459
  "items" => {
@@ -421,7 +463,7 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
421
463
  "properties" => {
422
464
  "service" => {
423
465
  "type" => "string",
424
- "enum" => %w{apigateway events s3 sns sqs dynamodb kinesis ses cognito alexa iot},
466
+ "enum" => %w{apigateway events s3 sns sqs dynamodb kinesis ses cognito alexa iot lex},
425
467
  "description" => "The name of the AWS service that will trigger this function"
426
468
  },
427
469
  "name" => {
@@ -437,6 +479,7 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
437
479
  },
438
480
  "code" => {
439
481
  "type" => "object",
482
+ "description" => "Zipped deployment package to upload to our function.",
440
483
  "properties" => {
441
484
  "s3_bucket" => {
442
485
  "type" => "string",
@@ -479,6 +522,28 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
479
522
  def self.validateConfig(function, configurator)
480
523
  ok = true
481
524
 
525
+ if function['triggers']
526
+ function['triggers'].each { |t|
527
+ mu_type = if t["service"] == "sns"
528
+ "notifiers"
529
+ elsif t["service"] == "apigateway"
530
+ "endpoints"
531
+ elsif t["service"] == "s3"
532
+ "buckets"
533
+ elsif t["service"] == "dynamodb"
534
+ "nosqldbs"
535
+ elsif t["service"] == "events"
536
+ "jobs"
537
+ elsif t["service"] == "sqs"
538
+ "msg_queues"
539
+ end
540
+
541
+ if mu_type
542
+ MU::Config.addDependency(function, t['name'], mu_type, no_create_wait: true)
543
+ end
544
+ }
545
+ end
546
+
482
547
  if function['vpc']
483
548
  fwname = "lambda-#{function['name']}"
484
549
  # default to allowing pings, if no ingress_rules were specified
@@ -502,14 +567,13 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
502
567
  function["add_firewall_rules"] << {"name" => fwname}
503
568
  function["permissions"] ||= []
504
569
  function["permissions"] << "network"
505
- function['dependencies'] ||= []
506
- function['dependencies'] << {
507
- "name" => fwname,
508
- "type" => "firewall_rule"
509
- }
570
+ MU::Config.addDependency(function, fwname, "firewall_rule")
510
571
  end
511
572
 
512
- if !function['iam_role']
573
+ function['role'] ||= function['iam_role']
574
+ function.delete("iam_role")
575
+
576
+ if !function['role']
513
577
  policy_map = {
514
578
  "basic" => "AWSLambdaBasicExecutionRole",
515
579
  "kinesis" => "AWSLambdaKinesisExecutionRole",
@@ -538,13 +602,21 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
538
602
  }
539
603
  configurator.insertKitten(roledesc, "roles")
540
604
 
541
- function['dependencies'] ||= []
542
- function['iam_role'] = function['name']+"execrole"
605
+ function['role'] = function['name']+"execrole"
543
606
 
544
- function['dependencies'] << {
545
- "type" => "role",
546
- "name" => function['name']+"execrole"
547
- }
607
+ end
608
+
609
+ if function['role'].is_a?(String)
610
+ function['role'] = MU::Config::Ref.get(
611
+ name: function['role'],
612
+ type: "roles",
613
+ cloud: "AWS",
614
+ credentials: function['credentials']
615
+ )
616
+ end
617
+
618
+ if function['role']['name']
619
+ MU::Config.addDependency(function, function['role']['name'], "role")
548
620
  end
549
621
 
550
622
  ok
@@ -552,23 +624,109 @@ MU.log shortname, MU::NOTICE, details: function.configuration.role
552
624
 
553
625
  private
554
626
 
555
- # Given an IAM role name, resolve to ARN. Will attempt to identify any
556
- # sibling Mu role resources by this name first, and failing that, will
557
- # do a plain get_role() to the IAM API for the provided name.
558
- # @param name [String]
559
- def get_role_arn(name)
560
- sib_role = @deploy.findLitterMate(name: name, type: "roles")
561
- return sib_role.cloudobj.arn if sib_role
627
+ def get_properties
628
+ role_obj = MU::Config::Ref.get(@config['role']).kitten(@deploy, cloud: "AWS")
629
+ raise MuError.new "Failed to fetch object from role reference", details: @config['role'].to_h if !role_obj
562
630
 
563
- begin
564
- role = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_role({
565
- role_name: name.to_s
566
- })
567
- return role['role']['arn']
568
- rescue Exception => e
569
- MU.log "#{e}", MU::ERR
631
+ lambda_properties = {
632
+ code: {},
633
+ function_name: @mu_name,
634
+ handler: @config['handler'],
635
+ publish: true,
636
+ role: role_obj.arn,
637
+ runtime: @config['runtime'],
638
+ }
639
+
640
+ if @config['code']['zip_file'] or @config['code']['path']
641
+ tempfile = nil
642
+ if @config['code']['path']
643
+ tempfile = Tempfile.new
644
+ MU.log "#{@mu_name} using code at #{@config['code']['path']}"
645
+ MU::Master.zipDir(@config['code']['path'], tempfile.path)
646
+ @config['code']['zip_file'] = tempfile.path
647
+ else
648
+ MU.log "#{@mu_name} using code packaged at #{@config['code']['zip_file']}"
649
+ end
650
+ zip = File.read(@config['code']['zip_file'])
651
+ @code_sha256 = Base64.encode64(Digest::SHA256.digest(zip)).chomp
652
+ lambda_properties[:code][:zip_file] = zip
653
+ if tempfile
654
+ tempfile.close
655
+ tempfile.unlink
656
+ end
657
+ else
658
+ lambda_properties[:code][:s3_bucket] = @config['code']['s3_bucket']
659
+ lambda_properties[:code][:s3_key] = @config['code']['s3_key']
660
+ if @config['code']['s3_object_version']
661
+ lambda_properties[:code][:s3_object_version] = @config['code']['s3_object_version']
662
+ end
663
+ # XXX need to download to a temporarily file, read it in, and calculate the digest in order to trigger updates in groom
664
+ end
665
+
666
+ if @config.has_key?('timeout')
667
+ lambda_properties[:timeout] = @config['timeout'].to_i ## secs
668
+ end
669
+
670
+ if @config.has_key?('memory')
671
+ lambda_properties[:memory_size] = @config['memory'].to_i
672
+ end
673
+
674
+ SIBLING_VARS.each_key { |sib_type|
675
+ siblings = @deploy.findLitterMate(return_all: true, type: sib_type, cloud: "AWS")
676
+ if siblings
677
+ siblings.each_value { |sibling|
678
+ metadata = sibling.notify
679
+ if !metadata
680
+ MU.log "Failed to extract metadata from sibling #{sibling}", MU::WARN
681
+ next
682
+ end
683
+ SIBLING_VARS[sib_type].each { |var|
684
+ if metadata[var]
685
+ @config['environment_variables'] ||= []
686
+ @config['environment_variables'] << {
687
+ "key" => (sibling.config['name']+"_"+var).gsub(/[^a-z0-9_]/i, '_'),
688
+ "value" => metadata[var]
689
+ }
690
+ end
691
+ }
692
+ }
693
+ end
694
+ }
695
+
696
+ if @config.has_key?('environment_variables')
697
+ lambda_properties[:environment] = {
698
+ variables: Hash[@config['environment_variables'].map { |v| [v['key'], v['value']] }]
699
+ }
570
700
  end
571
- nil
701
+
702
+ lambda_properties[:tags] = {}
703
+ MU::MommaCat.listStandardTags.each_pair { |k, v|
704
+ lambda_properties[:tags][k] = v
705
+ }
706
+ if @config['tags']
707
+ @config['tags'].each { |tag|
708
+ lambda_properties[:tags][tag.key.first] = tag.values.first
709
+ }
710
+ end
711
+
712
+ if @config.has_key?('vpc')
713
+ sgs = []
714
+ if @config['add_firewall_rules']
715
+ @config['add_firewall_rules'].each { |sg|
716
+ sg = @deploy.findLitterMate(type: "firewall_rule", name: sg['name'])
717
+ sgs << sg.cloud_id if sg and sg.cloud_id
718
+ }
719
+ end
720
+ if !@vpc
721
+ raise MuError, "Function #{@config['name']} had a VPC configured, but none was loaded"
722
+ end
723
+ lambda_properties[:vpc_config] = {
724
+ :subnet_ids => @vpc.subnets.map { |s| s.cloud_id },
725
+ :security_group_ids => sgs
726
+ }
727
+ end
728
+
729
+ lambda_properties
572
730
  end
573
731
 
574
732
  end