modulator 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,99 @@
1
+ module StackBuilder
2
+ module LambdaPolicy
3
+ module_function
4
+
5
+ # add inline iam role to lambda, NOTE: use the same role for all lambdas for now
6
+ def add_lambda_iam_role(function_name: nil)
7
+ StackBuilder.stack.add('LambdaRole', Humidifier::IAM::Role.new(
8
+ assume_role_policy_document: {
9
+ "Version" => "2012-10-17",
10
+ 'Statement' => [
11
+ {
12
+ "Action" => ["sts:AssumeRole"],
13
+ "Effect" => "Allow",
14
+ 'Principal' => {
15
+ 'Service' => ["lambda.amazonaws.com"]
16
+ }
17
+ }
18
+ ]
19
+ },
20
+ policies: []
21
+ )
22
+ )
23
+ end
24
+
25
+ def add_policy(policy, **opts)
26
+ StackBuilder.stack.resources['LambdaRole'].properties['policies'] << send(policy, opts)
27
+ end
28
+
29
+ # policy to access cloudwatch
30
+ def cloudwatch(**opts)
31
+ {
32
+ "policy_document" => {
33
+ "Version" => "2012-10-17",
34
+ 'Statement' => [
35
+ {
36
+ "Sid" => "AllowLogCreation",
37
+ "Action" => [
38
+ "logs:CreateLogStream",
39
+ "logs:PutLogEvents",
40
+ ],
41
+ "Effect" => "Allow",
42
+ "Resource" => Humidifier.fn.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*")
43
+ },
44
+ {
45
+ "Sid" => "AllowLogGroupCreation",
46
+ "Action" => [
47
+ "logs:CreateLogGroup",
48
+ ],
49
+ "Effect" => "Allow",
50
+ "Resource" => "*"
51
+ }
52
+ ]
53
+ },
54
+ "policy_name" => "cloud-watch-access"
55
+ }
56
+ end
57
+
58
+ # policy to access prefixed dynamo tables
59
+ def dynamo_db(**opts)
60
+ prefixes = opts[:prefixes] || []
61
+ prefix_separator = opts[:prefix_separator] || '-'
62
+ wildcard = '*'
63
+ if prefixes.any?
64
+ prefixes.map!{|prefix| prefix == :app_name ? StackBuilder.stack.app_name.dasherize.split('-') : prefix}
65
+ wildcard = "#{(prefixes << '*').join(prefix_separator)}"
66
+ end
67
+ {
68
+ "policy_document" => {
69
+ "Version" => "2012-10-17",
70
+ 'Statement' => [
71
+ {
72
+ "Sid" => "AllowAllActionsOnPrefixedTable",
73
+ "Effect" => "Allow",
74
+ "Action" => [
75
+ "dynamodb:*"
76
+ ],
77
+ "Resource" => Humidifier.fn.sub("arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/#{wildcard}")
78
+ },
79
+ {
80
+ "Sid" => "AdditionalPrivileges",
81
+ "Effect" => "Allow",
82
+ "Action" => [
83
+ "dynamodb:ListTables",
84
+ "dynamodb:DescribeTable"
85
+ ],
86
+ "Resource" => "*"
87
+ }
88
+ ]
89
+ },
90
+ "policy_name" => "dynamo-db-access"
91
+ }
92
+ end
93
+
94
+ # TODO: add access to named secrets
95
+ def secret_manager(**opts)
96
+
97
+ end
98
+ end
99
+ end
@@ -2,28 +2,49 @@ require 'aws-sdk-s3'
2
2
  require 'digest'
3
3
  require 'bundler'
4
4
 
5
- module AwsStackBuilder
5
+ module StackBuilder
6
6
  module_function
7
7
 
8
8
  S3Client = Aws::S3::Client.new
9
9
 
10
- def upload_lambda_handler
11
- bucket_name = Humidifier.ref("S3Bucket").reference
10
+ # bundle gems and upload all for simple lambda apps
11
+ def upload_lambda_files
12
+ puts '- bundling dependencies'
13
+ Bundler.with_clean_env do
14
+ Dir.chdir(app_path) do
15
+ `bundle install`
16
+ `bundle install --deployment --without development`
17
+ end
18
+ end
19
+ FileUtils.remove_dir(app_path.join("vendor/bundle/ruby/#{GEM_PATH_RUBY_VERSION}/cache")) # remove cache dir
20
+ upload_app_layer(sub_dirs: '', add_layer_to_stack: false) # reuse layer upload
21
+ FileUtils.remove_dir(app_path.join('.bundle'))
22
+ FileUtils.remove_dir(app_path.join('vendor'))
23
+ end
24
+
25
+ # generic handler for all lambda
26
+ def upload_generic_lambda_handler
12
27
  lambda_handler_key = LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
28
+ modulator_handler_source = Pathname.new(__FILE__).dirname.parent.join('lambda_handler.rb').read
13
29
  source = <<~SOURCE
14
- require 'modulator/lambda/aws_lambda_handler'
30
+ # DO NOT EDIT THIS FILE
31
+
32
+ #{modulator_handler_source}
15
33
  Dir.chdir('/opt/ruby/lib')
16
34
  SOURCE
17
35
 
18
36
  existing_handler = S3Client.get_object(
19
- bucket: bucket,
37
+ bucket: s3_bucket,
20
38
  key: lambda_handler_key
21
39
  ) rescue false # not found
22
40
 
23
- existing_source = Zip::InputStream.open(existing_handler.body) do |zip_file|
24
- zip_file.get_next_entry
25
- zip_file.read
26
- end if existing_handler
41
+ if existing_handler
42
+ existing_source = Zip::InputStream.open(existing_handler.body) do |zip_file|
43
+ zip_file.get_next_entry
44
+ zip_file.read
45
+ end
46
+ self.lambda_handler_s3_object_version = existing_handler.version_id
47
+ end
27
48
 
28
49
  if existing_source != source
29
50
  puts '- uploading generic lambda handler'
@@ -31,11 +52,12 @@ module AwsStackBuilder
31
52
  zip.put_next_entry LAMBDA_HANDLER_FILE_NAME + '.rb'
32
53
  zip.print source
33
54
  end
34
- S3Client.put_object(
35
- bucket: bucket,
55
+ new_handler = S3Client.put_object(
56
+ bucket: s3_bucket,
36
57
  key: lambda_handler_key,
37
58
  body: source_zip_file.tap(&:rewind).read
38
59
  )
60
+ self.lambda_handler_s3_object_version = new_handler.version_id
39
61
  end
40
62
  end
41
63
 
@@ -51,7 +73,7 @@ module AwsStackBuilder
51
73
  new_checksum = Digest::MD5.hexdigest(File.read(app_path.join('Gemfile.lock')))
52
74
 
53
75
  zip_file_name = app_dir + '_gems.zip'
54
- gems_path = app_path.join(hidden_dir, 'gems')
76
+ gems_path = app_path.join(hidden_dir, 'gems')
55
77
  gems_zip_path = app_path.join(hidden_dir, zip_file_name)
56
78
 
57
79
  if old_checksum != new_checksum
@@ -61,14 +83,14 @@ module AwsStackBuilder
61
83
  # bundle gems
62
84
  Bundler.with_clean_env do
63
85
  Dir.chdir(app_path) do
64
- `bundle install --path=./#{hidden_dir}/gems --clean`
86
+ `bundle install --path=./#{hidden_dir}/gems --clean --without development`
65
87
  end
66
88
  end
67
89
  ZipFileGenerator.new(gems_path, gems_zip_path).write
68
90
 
69
91
  # upload zipped file
70
92
  gem_layer = S3Client.put_object(
71
- bucket: bucket,
93
+ bucket: s3_bucket,
72
94
  key: zip_file_name,
73
95
  body: gems_zip_path.read
74
96
  )
@@ -77,7 +99,7 @@ module AwsStackBuilder
77
99
  gems_zip_path.delete
78
100
  else
79
101
  puts '- using existing gems layer'
80
- gem_layer = S3Client.get_object(bucket: bucket, key: zip_file_name)
102
+ gem_layer = S3Client.get_object(bucket: s3_bucket, key: zip_file_name)
81
103
  end
82
104
 
83
105
  add_layer(
@@ -88,48 +110,53 @@ module AwsStackBuilder
88
110
  )
89
111
  end
90
112
 
91
- def upload_app_layer
113
+ def upload_app_layer(sub_dirs: 'ruby/lib', add_layer_to_stack: true)
92
114
  zip_file_name = app_dir + '.zip'
93
115
  app_zip_path = app_path.join(hidden_dir, zip_file_name)
94
116
 
95
- # copy app code to ruby/lib in use outside temp dir
117
+ # copy app code to ruby/lib in outside temp dir
96
118
  temp_dir_name = '.modulator_temp'
97
- ruby_lib_dirs = 'ruby/lib'
119
+ temp_sub_dirs = sub_dirs
98
120
  temp_path = app_path.parent.join(temp_dir_name)
99
- temp_path.join(ruby_lib_dirs).mkpath
100
- FileUtils.copy_entry app_path, temp_path.join(ruby_lib_dirs)
121
+ temp_path.join(temp_sub_dirs).mkpath
122
+ FileUtils.copy_entry app_path, temp_path.join(temp_sub_dirs)
101
123
 
102
124
  # calculate checksum for app folder
103
125
  checksum_path = app_path.join(hidden_dir, 'app_checksum')
104
126
  old_checksum = (checksum_path.read rescue nil)
105
- new_checksum = checksum(app_path)
127
+ new_checksum = Utils.checksum(app_path)
106
128
 
107
129
  if old_checksum != new_checksum
108
- puts '- uploading app layer'
130
+ puts '- uploading app files'
109
131
  checksum_path.write(new_checksum)
110
132
  ZipFileGenerator.new(temp_path, app_zip_path).write
111
133
  # upload zipped file
112
134
  app_layer = S3Client.put_object(
113
- bucket: bucket,
135
+ bucket: s3_bucket,
114
136
  key: zip_file_name,
115
137
  body: app_zip_path.read
116
138
  )
117
139
  # delete zipped file
118
140
  app_zip_path.delete
119
141
  else
120
- puts '- using existing app layer'
121
- app_layer = S3Client.get_object(bucket: bucket, key: zip_file_name)
142
+ puts '- using existing app files'
143
+ app_layer = S3Client.get_object(bucket: s3_bucket, key: zip_file_name)
122
144
  end
123
145
 
124
146
  # delete temp dir
125
147
  FileUtils.remove_dir(temp_path)
126
148
 
127
- add_layer(
128
- name: app_name,
129
- description: "App source. MD5: #{new_checksum}",
130
- s3_key: zip_file_name,
131
- s3_object_version: app_layer.version_id
132
- )
149
+ if add_layer_to_stack
150
+ add_layer(
151
+ name: app_name,
152
+ description: "App source. MD5: #{new_checksum}",
153
+ s3_key: zip_file_name,
154
+ s3_object_version: app_layer.version_id
155
+ )
156
+ else # for simple lambda apps
157
+ self.lambda_handler_s3_key = zip_file_name
158
+ self.lambda_handler_s3_object_version = app_layer.version_id
159
+ end
133
160
  end
134
161
 
135
162
  # add layer
@@ -139,17 +166,11 @@ module AwsStackBuilder
139
166
  layer_name: name,
140
167
  description: description,
141
168
  content: {
142
- s3_bucket: bucket,
169
+ s3_bucket: s3_bucket,
143
170
  s3_key: s3_key,
144
171
  s3_object_version: s3_object_version
145
172
  }
146
173
  )
147
174
  )
148
175
  end
149
-
150
- def checksum(dir)
151
- files = Dir["#{dir}/**/*"].reject{|f| File.directory?(f)}
152
- content = files.map{|f| File.read(f)}.join
153
- Digest::MD5.hexdigest(content)
154
- end
155
176
  end
@@ -1,3 +1,3 @@
1
1
  module Modulator
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -4,7 +4,7 @@ require 'zip'
4
4
  # the patched classes are used only in tests and tools
5
5
  class String
6
6
  def camelize
7
- split('_').collect do |word|
7
+ gsub('-', '_').split('_').collect do |word|
8
8
  word[0] = word[0].upcase
9
9
  word
10
10
  end.join
@@ -56,6 +56,12 @@ module Utils
56
56
  def load_json(path)
57
57
  JSON.parse(File.read(path))
58
58
  end
59
+
60
+ def checksum(dir)
61
+ files = Dir["#{dir}/**/*"].reject{|f| File.directory?(f)}
62
+ content = files.map{|f| File.read(f)}.join
63
+ Digest::MD5.hexdigest(content)
64
+ end
59
65
  end
60
66
 
61
67
  # NOTE: this code is taken from https://github.com/rubyzip/rubyzip examples
@@ -29,10 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency "rack-test", "~> 1.1"
30
30
 
31
31
  # stack builder
32
- # custom source is not supported by bundler, it is added to gemfile
33
- # spec.add_dependency "humidifier", github: 'damir/humidifier'
34
- spec.add_dependency "aws-sdk-s3"
35
- spec.add_dependency "aws-sdk-cloudformation"
32
+ spec.add_dependency "humidifier"
36
33
  spec.add_dependency "rubyzip"
37
34
 
38
35
  # local gateway
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damir Roso
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-01 00:00:00.000000000 Z
11
+ date: 2019-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,21 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.1'
69
69
  - !ruby/object:Gem::Dependency
70
- name: aws-sdk-s3
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: aws-sdk-cloudformation
70
+ name: humidifier
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
@@ -160,6 +146,7 @@ files:
160
146
  - ".gitignore"
161
147
  - ".rspec"
162
148
  - ".travis.yml"
149
+ - CHANGELOG.md
163
150
  - CODE_OF_CONDUCT.md
164
151
  - Gemfile
165
152
  - Gemfile.lock
@@ -171,9 +158,11 @@ files:
171
158
  - lib/modulator.rb
172
159
  - lib/modulator/gateway/gateway.rb
173
160
  - lib/modulator/gateway/routes/console.rb
174
- - lib/modulator/lambda/aws_lambda_handler.rb
175
- - lib/modulator/lambda/aws_stack_builder.rb
176
- - lib/modulator/lambda/aws_stack_uploader.rb
161
+ - lib/modulator/gateway_event.json
162
+ - lib/modulator/lambda_handler.rb
163
+ - lib/modulator/stack/builder.rb
164
+ - lib/modulator/stack/policies.rb
165
+ - lib/modulator/stack/uploader.rb
177
166
  - lib/modulator/version.rb
178
167
  - lib/utils.rb
179
168
  - modulator.gemspec
@@ -1,225 +0,0 @@
1
- require 'humidifier'
2
- require_relative 'aws_stack_uploader'
3
-
4
- module AwsStackBuilder
5
- module_function
6
-
7
- RUBY_VERSION = 'ruby2.5'
8
- GEM_PATH = '/opt/ruby/2.5.0'
9
- LAMBDA_HANDLER_FILE_NAME = 'lambda-handler'
10
-
11
- class << self
12
- attr_accessor :app_name, :stack, :api_gateway_deployment, :gateway_id
13
- attr_accessor :app_path, :app_dir, :hidden_dir, :bucket, :stack_opts
14
- end
15
-
16
- def init(app_name:, bucket:, **stack_opts)
17
- puts 'Initializing stack'
18
- @app_name = app_name.camelize
19
- @bucket = bucket
20
- @app_path = Pathname.getwd
21
- @app_dir = app_path.basename.to_s
22
- @hidden_dir = '.modulator'
23
- @stack_opts = stack_opts
24
- @stack = Humidifier::Stack.new(name: @app_name, aws_template_format_version: '2010-09-09')
25
-
26
- # api stage
27
- @stack.add_parameter('ApiGatewayStageName', description: 'Gateway deployment stage', type: 'String', default: 'v1')
28
-
29
- add_api_gateway
30
- add_api_gateway_deployment
31
- add_lambda_iam_role
32
- upload_files
33
- extend_stack_instance(@stack)
34
- @stack
35
- end
36
-
37
- def upload_files
38
- upload_lambda_handler
39
- puts 'Generating layers'
40
- app_path.join(hidden_dir).mkpath
41
- upload_gems_layer
42
- upload_app_layer
43
- end
44
-
45
- # helpers
46
- def extend_stack_instance(stack)
47
- stack.instance_eval do
48
- def add_lambda_endpoint(**opts) # gateway:, mod:, wrapper: {}, env: {}, settings: {}
49
- # add lambda
50
- lambda = AwsStackBuilder.add_lambda(opts)
51
- # add api resources
52
- AwsStackBuilder.add_api_gateway_resources(gateway: opts[:gateway], lambda: lambda)
53
- end
54
- end
55
- end
56
-
57
- # gateway
58
- def add_api_gateway
59
- @gateway_id = 'ApiGateway'
60
- @stack.add(gateway_id, Humidifier::ApiGateway::RestApi.new(name: app_name, description: app_name + ' API'))
61
- end
62
-
63
- # gateway deployment
64
- def add_api_gateway_deployment
65
- @api_gateway_deployment = Humidifier::ApiGateway::Deployment.new(
66
- rest_api_id: Humidifier.ref(gateway_id),
67
- stage_name: Humidifier.ref("ApiGatewayStageName")
68
- )
69
- @stack.add('ApiGatewayDeployment', @api_gateway_deployment)
70
- @stack.add_output('ApiGatewayInvokeURL',
71
- value: Humidifier.fn.sub("https://${#{gateway_id}}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageName}"),
72
- description: 'API root url',
73
- export_name: app_name + 'RootUrl'
74
- )
75
- @api_gateway_deployment.depends_on = []
76
- end
77
-
78
- # lambda function
79
- def add_lambda(gateway:, mod:, wrapper: {}, env: {}, settings: {})
80
- lambda_config = {}
81
- name_parts = mod[:name].split('::')
82
- {gateway: gateway, module: mod, wrapper: wrapper}.each do |env_group_prefix, env_group|
83
- env_group.each{|env_key, env_value| lambda_config["#{env_group_prefix}_#{env_key}"] = env_value}
84
- end
85
-
86
- lambda_function = Humidifier::Lambda::Function.new(
87
- description: "Lambda for #{mod[:name]}.#{mod[:method]}",
88
- function_name: [app_name.dasherize, name_parts, mod[:method]].flatten.map(&:downcase).join('-'),
89
- handler: "#{LAMBDA_HANDLER_FILE_NAME}.AwsLambdaHandler.call",
90
- environment: {
91
- variables: env
92
- .reduce({}){|env_as_string, (k, v)| env_as_string.update(k.to_s => v.to_s)}
93
- .merge(lambda_config)
94
- .merge('GEM_PATH' => GEM_PATH, 'app_dir' => app_dir)
95
- },
96
- role: Humidifier.fn.get_att(['LambdaRole', 'Arn']),
97
- timeout: settings[:timeout] || stack_opts[:timeout] || 15,
98
- memory_size: settings[:memory_size] || stack_opts[:memory_size] || 128,
99
- runtime: RUBY_VERSION,
100
- code: {
101
- s3_bucket: bucket,
102
- s3_key: LAMBDA_HANDLER_FILE_NAME + '.rb.zip'
103
- },
104
- layers: [
105
- Humidifier.ref(app_name + 'Layer'),
106
- Humidifier.ref(app_name + 'GemsLayer')
107
- ]
108
- )
109
- id = ['Lambda', name_parts, mod[:method].capitalize].join
110
- @stack.add(id, lambda_function)
111
- add_lambda_invoke_permission(id: id, gateway: gateway)
112
- id
113
- end
114
-
115
- # invoke permission
116
- def add_lambda_invoke_permission(id:, gateway:)
117
- arn_path_matcher = gateway[:path].split('/').each_with_object([]) do |fragment, matcher|
118
- fragment = '*' if fragment.start_with?(':')
119
- matcher << fragment
120
- end.join('/')
121
- @stack.add('LambdaPermission', Humidifier::Lambda::Permission.new(
122
- action: "lambda:InvokeFunction",
123
- function_name: Humidifier.fn.get_att([id, 'Arn']),
124
- principal: "apigateway.amazonaws.com",
125
- source_arn: Humidifier.fn.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${#{gateway_id}}/*/#{gateway[:verb]}/#{arn_path_matcher}")
126
- )
127
- )
128
- end
129
-
130
- # gateway method
131
- def add_api_gateway_resources(gateway:, lambda:)
132
-
133
- # example: calculator/algebra/:x/:y/sum -> module name, args, method name
134
- path = gateway[:path].split('/')
135
-
136
- # root resource
137
- root_resource = path.shift
138
- @stack.add(root_resource.camelize, Humidifier::ApiGateway::Resource.new(
139
- rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
140
- parent_id: Humidifier.fn.get_att(["ApiGateway", "RootResourceId"]),
141
- path_part: root_resource
142
- )
143
- )
144
-
145
- # args and method name are nested resources
146
- parent_resource = root_resource.camelize
147
- path.each do |fragment|
148
- if fragment.start_with?(':')
149
- fragment = fragment[1..-1]
150
- dynamic_fragment = "{#{fragment}}"
151
- end
152
- @stack.add(parent_resource + fragment.camelize, Humidifier::ApiGateway::Resource.new(
153
- rest_api_id: Humidifier.ref(AwsStackBuilder.gateway_id),
154
- parent_id: Humidifier.ref(parent_resource),
155
- path_part: dynamic_fragment || fragment
156
- )
157
- )
158
- parent_resource = parent_resource + fragment.camelize
159
- end
160
-
161
- # attach lambda to last resource
162
- id = 'EndpointFor' + (gateway[:path].gsub(':', '').gsub('/', '_')).camelize
163
- @stack.add(id, Humidifier::ApiGateway::Method.new(
164
- authorization_type: 'NONE',
165
- http_method: gateway[:verb].to_s.upcase,
166
- integration: {
167
- integration_http_method: 'POST',
168
- type: "AWS_PROXY",
169
- uri: Humidifier.fn.sub([
170
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations",
171
- 'lambdaArn' => Humidifier.fn.get_att([lambda, 'Arn'])
172
- ])
173
- },
174
- rest_api_id: Humidifier.ref(gateway_id),
175
- resource_id: Humidifier.ref(parent_resource) # last evaluated resource
176
- )
177
- )
178
-
179
- # deployment depends on the method
180
- @api_gateway_deployment.depends_on << id
181
- end
182
-
183
- def add_lambda_iam_role(function_name: nil)
184
- @stack.add('LambdaRole', Humidifier::IAM::Role.new(
185
- assume_role_policy_document: {
186
- 'Version' => "2012-10-17",
187
- 'Statement' => [
188
- {
189
- 'Action' => ["sts:AssumeRole"],
190
- 'Effect' => "Allow",
191
- 'Principal' => {
192
- 'Service' => ["lambda.amazonaws.com"]
193
- }
194
- }
195
- ]
196
- },
197
- policies: [
198
- {
199
- 'policy_document' => {
200
- 'Version' => "2012-10-17",
201
- 'Statement' => [
202
- {
203
- 'Action' => [
204
- "logs:CreateLogStream",
205
- "logs:PutLogEvents",
206
- ],
207
- 'Effect' => "Allow",
208
- 'Resource' => Humidifier.fn.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*")
209
- },
210
- {
211
- 'Action' => [
212
- "logs:CreateLogGroup",
213
- ],
214
- 'Effect' => "Allow",
215
- 'Resource' => "*"
216
- }
217
- ]
218
- },
219
- 'policy_name' => "cloud-watch-access"
220
- }
221
- ]
222
- )
223
- )
224
- end
225
- end