sumomo 0.8.3 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # For functions that do not conform to Ruby's naming convention
4
+ # of funcs must be snake_case
5
+ module HasIrregularlyNamedFunctions
6
+ def defi(name, &block)
7
+ define_method(name, &block)
8
+ end
9
+ end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Momo
3
- class Resource
4
- def exec_role
5
- res = Momo::Resource.new("AWS::IAM::Role", "LambdaFunctionExecutionRole", @stack)
6
- res.complete!
7
- res
8
- end
9
- end
4
+ class Resource
5
+ def exec_role
6
+ res = Momo::Resource.new('AWS::IAM::Role', 'LambdaFunctionExecutionRole', @stack)
7
+ res.complete!
8
+ res
9
+ end
10
+ end
10
11
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Momo
3
- class Stack
4
- alias_method :stack_make, :make
5
- end
4
+ class Stack
5
+ alias stack_make make
6
+ end
6
7
  end
@@ -1,108 +1,111 @@
1
- require "active_support/inflector"
2
- require "hashie"
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+ require 'hashie'
3
5
 
4
6
  module Sumomo
5
- module Stack
6
-
7
- def make_network(layers: [])
8
-
9
- zones = get_azs()
10
-
11
- region = @region
12
-
13
- vpc = make "AWS::EC2::VPC" do
14
- CidrBlock "10.0.0.0/16"
15
- EnableDnsSupport true
16
- EnableDnsHostnames true
17
- tag "Name", call("Fn::Join", "-", [ref("AWS::StackName")])
18
- end
19
-
20
- gateway = make "AWS::EC2::InternetGateway" do
21
- tag "Name", call("Fn::Join", "-", [ref("AWS::StackName")])
22
- end
23
-
24
- attachment = make "AWS::EC2::VPCGatewayAttachment" do
25
- VpcId vpc
26
- InternetGatewayId gateway
27
- end
28
-
29
- inet_route_table = make "AWS::EC2::RouteTable" do
30
- depends_on attachment
31
- VpcId vpc
32
- tag "Name", call("Fn::Join", "-", ["public", ref("AWS::StackName")])
33
- end
34
-
35
- make "AWS::EC2::Route" do
36
- RouteTableId inet_route_table
37
- DestinationCidrBlock "0.0.0.0/0"
38
- GatewayId gateway
39
- end
40
-
41
- last_unused_number = 0
42
-
43
- subnet_numbers = []
44
-
45
- #load current config
46
- number_hash = {}
47
- subnet_hash = {}
48
-
49
- ec2 = Aws::EC2::Client.new(region: @region)
50
- ec2_subnets = ec2.describe_subnets().subnets
51
- ec2_subnets.each do |subnet|
52
- if subnet.tags.select {|x| x.key == "aws:cloudformation:stack-name" && x.value == @bucket_name}.length == 1
53
- layer = /^#{@bucket_name}-(?<layer_name>.+)-[a-z]+$/.match(subnet.tags.select{|x| x.key == "Name"}.first.value)[:layer_name]
54
- zone = subnet.availability_zone
55
- number = /^10.0.(?<num>[0-9]+).0/.match(subnet.cidr_block)[:num].to_i
56
-
57
- key = "#{layer}/#{zone}"
58
- number_hash[number] = key
59
- subnet_hash[key] = number
60
- end
61
- end
62
-
63
- # assign numbers to unassigned subnets
64
- layers.product(zones).each do |e|
65
- key = "#{e[0]}/#{e[1]}"
66
- if !subnet_hash.has_key?(key)
67
- loop do
68
- break if !number_hash.has_key?(last_unused_number)
69
- last_unused_number += 1
70
- end
71
- number_hash[last_unused_number] = key
72
- subnet_hash[key] = last_unused_number
73
- subnet_numbers << [e, last_unused_number]
74
- else
75
- subnet_numbers << [e, subnet_hash[key]]
76
- end
77
- end
78
-
79
- subnets = {}
80
-
81
- subnet_numbers.each do |e, subnet_number|
82
- layer = e[0]
83
- zone = e[1]
84
-
85
- zone_letter = zone.sub("#{region}", "")
86
- cidr = "10.0.#{subnet_number}.0/24"
87
-
88
- subnet = make "AWS::EC2::Subnet", name: "SubnetFor#{layer.camelize}Layer#{zone_letter.upcase}" do
89
- AvailabilityZone zone
90
- VpcId vpc
91
- CidrBlock cidr
92
-
93
- tag("Name", call("Fn::Join", "-", [ ref("AWS::StackName"), "#{layer}", zone_letter] ) )
94
- end
95
-
96
- make "AWS::EC2::SubnetRouteTableAssociation", name: "SubnetRTAFor#{layer.camelize}Layer#{zone_letter.upcase}" do
97
- SubnetId subnet
98
- RouteTableId inet_route_table
99
- end
100
-
101
- subnets[layer] ||= []
102
- subnets[layer] << {name: subnet, cidr: cidr, zone: zone}
103
- end
104
-
105
- Hashie::Mash.new vpc: vpc, subnets: subnets, azs: zones, attachment: attachment
106
- end
107
- end
108
- end
7
+ module Stack
8
+ def make_network(layers: [])
9
+ zones = get_azs
10
+
11
+ region = @region
12
+
13
+ vpc = make 'AWS::EC2::VPC' do
14
+ CidrBlock '10.0.0.0/16'
15
+ EnableDnsSupport true
16
+ EnableDnsHostnames true
17
+ tag 'Name', call('Fn::Join', '-', [ref('AWS::StackName')])
18
+ end
19
+
20
+ gateway = make 'AWS::EC2::InternetGateway' do
21
+ tag 'Name', call('Fn::Join', '-', [ref('AWS::StackName')])
22
+ end
23
+
24
+ attachment = make 'AWS::EC2::VPCGatewayAttachment' do
25
+ VpcId vpc
26
+ InternetGatewayId gateway
27
+ end
28
+
29
+ inet_route_table = make 'AWS::EC2::RouteTable' do
30
+ depends_on attachment
31
+ VpcId vpc
32
+ tag 'Name', call('Fn::Join', '-', ['public', ref('AWS::StackName')])
33
+ end
34
+
35
+ make 'AWS::EC2::Route' do
36
+ RouteTableId inet_route_table
37
+ DestinationCidrBlock '0.0.0.0/0'
38
+ GatewayId gateway
39
+ end
40
+
41
+ last_unused_number = 0
42
+
43
+ subnet_numbers = []
44
+
45
+ # load current config
46
+ number_hash = {}
47
+ subnet_hash = {}
48
+
49
+ ec2 = Aws::EC2::Client.new(region: @region)
50
+ ec2_subnets = ec2.describe_subnets.subnets
51
+ ec2_subnets.each do |subnet|
52
+ unless subnet.tags.select { |x| x.key == 'aws:cloudformation:stack-name' && x.value == @bucket_name }.length == 1
53
+ next
54
+ end
55
+
56
+ layer = /^#{@bucket_name}-(?<layer_name>.+)-[a-z]+$/.match(subnet.tags.select { |x| x.key == 'Name' }.first.value)[:layer_name]
57
+ zone = subnet.availability_zone
58
+ number = /^10.0.(?<num>[0-9]+).0/.match(subnet.cidr_block)[:num].to_i
59
+
60
+ key = "#{layer}/#{zone}"
61
+ number_hash[number] = key
62
+ subnet_hash[key] = number
63
+ end
64
+
65
+ # assign numbers to unassigned subnets
66
+ layers.product(zones).each do |e|
67
+ key = "#{e[0]}/#{e[1]}"
68
+ if !subnet_hash.key?(key)
69
+ loop do
70
+ break unless number_hash.key?(last_unused_number)
71
+
72
+ last_unused_number += 1
73
+ end
74
+ number_hash[last_unused_number] = key
75
+ subnet_hash[key] = last_unused_number
76
+ subnet_numbers << [e, last_unused_number]
77
+ else
78
+ subnet_numbers << [e, subnet_hash[key]]
79
+ end
80
+ end
81
+
82
+ subnets = {}
83
+
84
+ subnet_numbers.each do |e, subnet_number|
85
+ layer = e[0]
86
+ zone = e[1]
87
+
88
+ zone_letter = zone.sub(region.to_s, '')
89
+ cidr = "10.0.#{subnet_number}.0/24"
90
+
91
+ subnet = make 'AWS::EC2::Subnet', name: "SubnetFor#{layer.camelize}Layer#{zone_letter.upcase}" do
92
+ AvailabilityZone zone
93
+ VpcId vpc
94
+ CidrBlock cidr
95
+
96
+ tag('Name', call('Fn::Join', '-', [ref('AWS::StackName'), layer.to_s, zone_letter]))
97
+ end
98
+
99
+ make 'AWS::EC2::SubnetRouteTableAssociation', name: "SubnetRTAFor#{layer.camelize}Layer#{zone_letter.upcase}" do
100
+ SubnetId subnet
101
+ RouteTableId inet_route_table
102
+ end
103
+
104
+ subnets[layer] ||= []
105
+ subnets[layer] << { name: subnet, cidr: cidr, zone: zone }
106
+ end
107
+
108
+ Hashie::Mash.new vpc: vpc, subnets: subnets, azs: zones, attachment: attachment
109
+ end
110
+ end
111
+ end
@@ -1,192 +1,194 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Sumomo
3
- module Stack
4
-
5
- def hidden_value(value)
6
- name = make_default_resource_name("HiddenValue")
7
- if !@hidden_values
8
- @hidden_values = []
9
- end
10
-
11
- @hidden_values << {
12
- parameter_key: name,
13
- parameter_value: value
14
- }
15
-
16
- param name, type: :string
17
- end
18
-
19
- def upload_file(name,content)
20
- @store.set_raw("uploads/#{name}", content)
21
- puts "Uploaded #{name}"
22
- end
23
-
24
- def make_lambda(name: nil, files:[{name:"index.js", code:""}],
25
- description: "Lambda Function in #{@bucket_name}",
26
- function_key: "cloudformation/lambda/function_#{name}",
27
- handler: "index.handler",
28
- runtime: "nodejs8.10",
29
- memory_size: 128,
30
- timeout: 30,
31
- with_statements: [])
32
-
33
- name ||= make_default_resource_name("Lambda")
34
-
35
- stringio = Zip::OutputStream.write_buffer do |zio|
36
- files.each do |file|
37
- zio.put_next_entry(file[:name])
38
- if file[:code]
39
- zio.write file[:code]
40
- elsif file[:path]
41
- zio.write File.read(file[:path])
42
- else
43
- raise "Files needs to be an array of objects with :name and :code or :path members"
44
- end
45
- end
46
- end
47
-
48
- @store.set_raw(function_key, stringio.string)
49
-
50
- stack = self
51
-
52
- code_location = {"S3Bucket": @bucket_name, "S3Key": function_key}
53
- fun = make "AWS::Lambda::Function", name: name do
54
- Code code_location
55
- Description description
56
- MemorySize memory_size
57
- Handler handler
58
- Runtime runtime
59
- Timeout timeout
60
- Role stack.exec_role(with_statements: with_statements).Arn
61
- end
62
-
63
- log_group = make "AWS::Logs::LogGroup", name: "#{name}LogGroup" do
64
- LogGroupName call("Fn::Join", "", ["/aws/lambda/", fun])
65
- RetentionInDays 30
66
- end
67
-
68
- fun
69
- end
70
-
71
- def define_custom_resource(name: nil,code:)
72
-
73
- name ||= make_default_resource_name("CustomResource")
74
-
75
- func = make_lambda(
76
- name: name,
77
- files:[
78
- {
79
- name: "index.js",
80
- code: File.read( File.join(Gem.loaded_specs['sumomo'].full_gem_path, "data", "sumomo", "custom_resource_utils.js") ).sub("{{ CODE }}", code)
81
- }
82
- ],
83
- description: "CF Resource Custom::#{name}",
84
- function_key: "cloudformation/custom_resources/function_#{name}")
85
-
86
- @custom_resources["Custom::#{name}"] = func
87
- end
88
-
89
- def make_custom(custom_resource, options = {}, &block)
90
- bucket_name = @bucket_name
91
- stack_make "Custom::#{custom_resource.name}", options do
92
- ServiceToken custom_resource.Arn
93
- Region ref("AWS::Region")
94
- Bucket bucket_name
95
- instance_eval(&block) if block
96
- end
97
- end
98
-
99
- def make(type, options = {}, &block)
100
- match = /^Custom\:\:(?<name>[a-z0-9]+)/i.match(type)
101
- if match
102
- if !@custom_resources[type]
103
-
104
- resource_function_source = File.join(Gem.loaded_specs['sumomo'].full_gem_path, "data", "sumomo", "custom_resources", "#{match[:name]}.js")
105
-
106
- if File.exists? resource_function_source
107
- define_custom_resource(name: match[:name], code: File.read(resource_function_source))
108
- else
109
- throw "#{resource_function_source} does not exist"
110
-
111
- end
112
- end
113
- make_custom(@custom_resources[type], options, &block)
114
- else
115
- stack_make(type, options, &block)
116
- end
117
- end
118
-
119
- def exec_role(with_statements: [])
120
-
121
- if @exec_roles == nil
122
- @exec_roles = {}
123
- end
124
-
125
- statement_key = JSON.parse(with_statements.to_json)
126
-
127
- if !@exec_roles.has_key?(statement_key)
128
- name = make_default_resource_name("LambdaExecRole")
129
-
130
- role_policy_doc = {
131
- "Version" => "2012-10-17",
132
- "Statement" => [{
133
- "Effect" => "Allow",
134
- "Principal" => {"Service" => ["edgelambda.amazonaws.com", "lambda.amazonaws.com"]},
135
- "Action" => ["sts:AssumeRole"]
136
- }]
137
- }
138
-
139
- bucket_name = @bucket_name
140
-
141
- statement_list = [
142
- {
143
- "Effect" => "Allow",
144
- "Action" => ["logs:CreateLogStream","logs:PutLogEvents"],
145
- "Resource" => "arn:aws:logs:*:*:*"
146
- },
147
- {
148
- "Effect" => "Allow",
149
- "Action" => ["cloudformation:DescribeStacks", "ec2:Describe*", ],
150
- "Resource" => "*"
151
- },
152
- {
153
- "Effect" => "Allow",
154
- "Action" => ["s3:DeleteObject", "s3:GetObject", "s3:PutObject"],
155
- "Resource" => "arn:aws:s3:::#{bucket_name}/*"
156
- },
157
- {
158
- "Effect" => "Allow",
159
- "Action" => ["cloudfront:CreateCloudFrontOriginAccessIdentity", "cloudfront:DeleteCloudFrontOriginAccessIdentity"],
160
- "Resource" => "*"
161
- },
162
- {
163
- "Effect" => "Allow",
164
- "Action" => ["apigateway:*", "cloudfront:UpdateDistribution"],
165
- "Resource" => "*"
166
- },
167
- {
168
- "Effect" => "Allow",
169
- "Action" => ["acm:RequestCertificate", "acm:DeleteCertificate", "acm:DescribeCertificate"],
170
- "Resource" => "*"
171
- }] + with_statements
172
-
173
- @exec_roles[statement_key] = make "AWS::IAM::Role", name: name do
174
- AssumeRolePolicyDocument role_policy_doc
175
- Path "/"
176
- Policies [
177
- {
178
- "PolicyName" => name,
179
- "PolicyDocument" => {
180
- "Version" => "2012-10-17",
181
- "Statement" => statement_list
182
- }
183
- }
184
- ]
185
- end
186
- end
187
-
188
- @exec_roles[statement_key]
189
-
190
- end
191
- end
4
+ module Stack
5
+ def hidden_value(value)
6
+ name = make_default_resource_name('HiddenValue')
7
+ @hidden_values ||= []
8
+
9
+ @hidden_values << {
10
+ parameter_key: name,
11
+ parameter_value: value
12
+ }
13
+
14
+ param name, type: :string
15
+ end
16
+
17
+ def upload_file(name, content)
18
+ @store.set_raw("uploads/#{name}", content)
19
+ puts "Uploaded #{name}"
20
+ end
21
+
22
+ def make_lambda(name: nil, files: [{ name: 'index.js', code: '' }],
23
+ description: "Lambda Function in #{@bucket_name}",
24
+ function_key: "cloudformation/lambda/function_#{name}",
25
+ handler: 'index.handler',
26
+ runtime: 'nodejs10.x',
27
+ memory_size: 128,
28
+ timeout: 30,
29
+ role: nil)
30
+
31
+ name ||= make_default_resource_name('Lambda')
32
+ role ||= custom_resource_exec_role
33
+
34
+ stringio = Zip::OutputStream.write_buffer do |zio|
35
+ files.each do |file|
36
+ zio.put_next_entry(file[:name])
37
+ if file[:code]
38
+ zio.write file[:code]
39
+ elsif file[:path]
40
+ zio.write File.read(file[:path])
41
+ else
42
+ raise 'Files needs to be an array of objects with :name and :code or :path members'
43
+ end
44
+ end
45
+ end
46
+
47
+ @store.set_raw(function_key, stringio.string)
48
+
49
+ stack = self
50
+
51
+ code_location = { "S3Bucket": @bucket_name, "S3Key": function_key }
52
+ fun = make 'AWS::Lambda::Function', name: name do
53
+ Code code_location
54
+ Description description
55
+ MemorySize memory_size
56
+ Handler handler
57
+ Runtime runtime
58
+ Timeout timeout
59
+ Role role.Arn
60
+ end
61
+
62
+ log_group = make 'AWS::Logs::LogGroup', name: "#{name}LogGroup" do
63
+ LogGroupName call('Fn::Join', '', ['/aws/lambda/', fun])
64
+ RetentionInDays 30
65
+ end
66
+
67
+ fun
68
+ end
69
+
70
+ def define_custom_resource(name: nil, code:, role: nil)
71
+ name ||= make_default_resource_name('CustomResource')
72
+ role ||= custom_resource_exec_role
73
+
74
+ func = make_lambda(
75
+ name: name,
76
+ role: role,
77
+ files: [
78
+ {
79
+ name: 'index.js',
80
+ code: File.read(File.join(Gem.loaded_specs['sumomo'].full_gem_path, 'data', 'sumomo', 'custom_resource_utils.js')).sub('{{ CODE }}', code)
81
+ }
82
+ ],
83
+ description: "CF Resource Custom::#{name}",
84
+ function_key: "cloudformation/custom_resources/function_#{name}"
85
+ )
86
+
87
+ @custom_resources["Custom::#{name}"] = func
88
+ end
89
+
90
+ def make_custom(custom_resource, options = {}, &block)
91
+ bucket_name = @bucket_name
92
+ stack_make "Custom::#{custom_resource.name}", options do
93
+ ServiceToken custom_resource.Arn
94
+ Region ref('AWS::Region')
95
+ Bucket bucket_name
96
+ instance_eval(&block) if block
97
+ end
98
+ end
99
+
100
+ def make(type, options = {}, &block)
101
+ match = /^Custom\:\:(?<name>[a-z0-9]+)/i.match(type)
102
+ if match
103
+ unless @custom_resources[type]
104
+
105
+ resource_function_source = File.join(Gem.loaded_specs['sumomo'].full_gem_path, 'data', 'sumomo', 'custom_resources', "#{match[:name]}.js")
106
+
107
+ if File.exist? resource_function_source
108
+ define_custom_resource(name: match[:name], code: File.read(resource_function_source))
109
+ else
110
+ throw "#{resource_function_source} does not exist"
111
+
112
+ end
113
+ end
114
+ make_custom(@custom_resources[type], options, &block)
115
+ else
116
+ stack_make(type, options, &block)
117
+ end
118
+ end
119
+
120
+ def lambda_exec_role(statements: [], principals: [])
121
+ name = make_default_resource_name('LambdaExecRole')
122
+
123
+ role_policy_doc = {
124
+ 'Version' => '2012-10-17',
125
+ 'Statement' => [{
126
+ 'Effect' => 'Allow',
127
+ 'Principal' => { 'Service' => principals },
128
+ 'Action' => ['sts:AssumeRole']
129
+ }]
130
+ }
131
+
132
+ make 'AWS::IAM::Role', name: name do
133
+ AssumeRolePolicyDocument role_policy_doc
134
+ Path '/'
135
+ Policies [
136
+ {
137
+ 'PolicyName' => name,
138
+ 'PolicyDocument' => {
139
+ 'Version' => '2012-10-17',
140
+ 'Statement' => statements
141
+ }
142
+ }
143
+ ]
144
+ end
145
+ end
146
+
147
+ def custom_resource_exec_role(with_statements: [])
148
+ @exec_roles ||= {}
149
+
150
+ statement_key = JSON.parse(with_statements.to_json)
151
+
152
+ @exec_roles[statement_key] ||= lambda_exec_role(
153
+ principals: ['edgelambda.amazonaws.com', 'lambda.amazonaws.com'],
154
+ statements: [
155
+ {
156
+ 'Effect' => 'Allow',
157
+ 'Action' => ['logs:CreateLogStream', 'logs:PutLogEvents'],
158
+ 'Resource' => 'arn:aws:logs:*:*:*'
159
+ },
160
+ {
161
+ 'Effect' => 'Allow',
162
+ 'Action' => ['cloudformation:DescribeStacks', 'ec2:Describe*'],
163
+ 'Resource' => '*'
164
+ },
165
+ {
166
+ 'Effect' => 'Allow',
167
+ 'Action' => ['s3:DeleteObject', 's3:GetObject', 's3:PutObject'],
168
+ 'Resource' => "arn:aws:s3:::#{@bucket_name}/*"
169
+ },
170
+ {
171
+ 'Effect' => 'Allow',
172
+ 'Action' => ['cloudfront:CreateCloudFrontOriginAccessIdentity', 'cloudfront:DeleteCloudFrontOriginAccessIdentity'],
173
+ 'Resource' => '*'
174
+ },
175
+ {
176
+ 'Effect' => 'Allow',
177
+ 'Action' => ['apigateway:*', 'cloudfront:UpdateDistribution'],
178
+ 'Resource' => '*'
179
+ },
180
+ {
181
+ 'Effect' => 'Allow',
182
+ 'Action' => ['acm:RequestCertificate', 'acm:DeleteCertificate', 'acm:DescribeCertificate'],
183
+ 'Resource' => '*'
184
+ },
185
+ {
186
+ 'Effect' => 'Allow',
187
+ 'Action' => ['s3:*'],
188
+ 'Resource' => 'arn:aws:s3:::*'
189
+ }
190
+ ] + with_statements
191
+ )
192
+ end
193
+ end
192
194
  end