sumomo 0.8.4 → 0.8.6

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.
@@ -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: 'nodejs8.10',
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