lono 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ require 'json'
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+ require 'lono/cli'
5
+ require 'lono/task'
6
+ require 'lono/dsl'
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+
3
+ module Lono
4
+ class CLI < Thor
5
+
6
+ desc "init", "Setup lono project"
7
+ long_desc "Sets up config/lono.rb"
8
+ def init
9
+ Lono::Task.init
10
+ end
11
+
12
+ desc "generate", "Generate the cloud formation templates"
13
+ long_desc <<EOL
14
+ Examples:
15
+
16
+ 1. lono generate
17
+
18
+ Builds the cloud formation templates files based on config/lono.rb and writes them to the output folder on the filesystem.
19
+ EOL
20
+ def generate
21
+ Lono::Task.generate(options.dup.merge(:verbose => true))
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,69 @@
1
+ require 'erb'
2
+
3
+ module Lono
4
+ class DSL
5
+ def initialize(options={})
6
+ @options = options
7
+ @path = options[:config_path] || 'config/lono.rb'
8
+ @templates = []
9
+ @results = {}
10
+ end
11
+
12
+ def evaluate
13
+ instance_eval(File.read(@path), @path)
14
+ end
15
+
16
+ def template(name, &block)
17
+ @templates << {:name => name, :block => block}
18
+ end
19
+
20
+ def build
21
+ @templates.each do |t|
22
+ @results[t[:name]] = Template.new(t[:name], t[:block], @options).build
23
+ end
24
+ end
25
+
26
+ def output(options={})
27
+ output_path = options[:output_path] || 'output'
28
+ FileUtils.mkdir(output_path) unless File.exist?(output_path)
29
+ puts "Generating Cloud Formation templates:" if options[:verbose]
30
+ @results.each do |name,json|
31
+ path = "#{output_path}/#{name}"
32
+ puts " #{path}" if options[:verbose]
33
+ File.open(path, 'w') {|f| f.write(json) }
34
+ end
35
+ end
36
+
37
+ def run(options={})
38
+ evaluate
39
+ build
40
+ options.empty? ? output : output(options)
41
+ end
42
+ end
43
+
44
+ class Template
45
+ include ERB::Util
46
+ def initialize(name, block, options={})
47
+ @name = name
48
+ @block = block
49
+ @options = options
50
+ @options[:project_root] ||= '.'
51
+ end
52
+
53
+ def build
54
+ instance_eval(&@block)
55
+ template = IO.read(@source)
56
+ ERB.new(template).result(binding)
57
+ end
58
+
59
+ def source(path)
60
+ @source = "#{@options[:project_root]}/templates/#{path}"
61
+ end
62
+
63
+ def variables(vars={})
64
+ vars.each do |var,value|
65
+ instance_variable_set("@#{var}", value)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,36 @@
1
+ module Lono
2
+ class Task
3
+ def self.init(options={})
4
+ project_root = options[:project_root] || '.'
5
+ puts "Settin up lono project" unless options[:quiet]
6
+ %w[Guardfile config/lono.rb templates/app.json.erb].each do |name|
7
+ source = File.expand_path("../../files/#{name}", __FILE__)
8
+ dirname = File.dirname(name)
9
+ FileUtils.mkdir(dirname) unless File.exist?(dirname)
10
+ dest = "#{project_root}/#{name}"
11
+
12
+ if File.exist?(dest)
13
+ puts "already exists: #{dest}" unless options[:quiet]
14
+ else
15
+ puts "creating: #{dest}" unless options[:quiet]
16
+ FileUtils.cp(source, dest)
17
+ end
18
+ end
19
+ end
20
+ def self.generate(options)
21
+ new(options).generate
22
+ end
23
+
24
+ def initialize(options={})
25
+ @options = options
26
+ if options.empty?
27
+ @dsl = DSL.new
28
+ else
29
+ @dsl = DSL.new(options)
30
+ end
31
+ end
32
+ def generate
33
+ @dsl.run(@options)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Lono
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/lono/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Tung Nguyen"]
6
+ gem.email = ["tongueroo@gmail.com"]
7
+ gem.description = %q{Lono generates cloud formation templates based on erb templates.}
8
+ gem.summary = %q{Lono generates cloud formation templates based on erb templates.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "lono"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Lono::VERSION
17
+
18
+ gem.add_dependency "rake"
19
+ gem.add_dependency "json"
20
+ gem.add_dependency "thor"
21
+ gem.add_dependency "aws-sdk"
22
+ gem.add_dependency 'guard'
23
+ gem.add_dependency 'rb-fsevent'
24
+ gem.add_dependency "guard-cloudformation"
25
+ gem.add_dependency "guard-lono"
26
+
27
+ gem.add_development_dependency 'rspec'
28
+ gem.add_development_dependency 'guard-rspec'
29
+ gem.add_development_dependency 'guard-bundler'
30
+
31
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe Lono do
4
+ before(:each) do
5
+ @project_root = File.expand_path("../../project", __FILE__)
6
+ @dsl = Lono::DSL.new(
7
+ :config_path => "#{@project_root}/config/lono.rb",
8
+ :project_root => @project_root
9
+ )
10
+ @dsl.evaluate
11
+ end
12
+
13
+ after(:each) do
14
+ FileUtils.rm_rf("#{@project_root}/output")
15
+ end
16
+
17
+ it "should generate cloud formation template" do
18
+ @dsl.build
19
+ @dsl.output(:output_path => "#{@project_root}/output")
20
+ raw = IO.read("#{@project_root}/output/prod-api-app.json")
21
+ json = JSON.load(raw)
22
+ json['Description'].should == "Api Stack"
23
+ json['Mappings']['AWSRegionArch2AMI']['us-east-1']['64'].should == 'ami-123'
24
+ end
25
+ end
26
+
27
+ describe Lono::Task do
28
+ before(:each) do
29
+ @project_root = File.expand_path("../../project", __FILE__)
30
+ end
31
+
32
+ after(:each) do
33
+ FileUtils.rm_rf("#{@project_root}/output")
34
+ end
35
+
36
+ it "task should generate cloud formation templates" do
37
+ Lono::Task.generate(
38
+ :project_root => @project_root,
39
+ :config_path => "#{@project_root}/config/lono.rb",
40
+ :output_path => "#{@project_root}/output"
41
+ )
42
+ raw = IO.read("#{@project_root}/output/prod-api-app.json")
43
+ json = JSON.load(raw)
44
+ json['Description'].should == "Api Stack"
45
+ json['Mappings']['AWSRegionArch2AMI']['us-east-1']['64'].should == 'ami-123'
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ template "prod-api-app.json" do
2
+ source "app.json.erb"
3
+ variables(
4
+ :env => 'prod',
5
+ :app => 'api',
6
+ :role => "app",
7
+ :ami => "ami-123",
8
+ :instance_type => "c1.xlarge",
9
+ :port => "80",
10
+ :high_threshold => "15",
11
+ :high_periods => "4",
12
+ :low_threshold => "5",
13
+ :low_periods => "10",
14
+ :max_size => "24",
15
+ :min_size => "6",
16
+ :down_adjustment => "-3",
17
+ :up_adjustment => "3",
18
+ :ssl_cert => "arn:aws:iam::12345:server-certificate/wildcard"
19
+ )
20
+ end
21
+ template "prod-br-app.json" do
22
+ source "app.json.erb"
23
+ variables(
24
+ :env => "prod",
25
+ :app => 'br',
26
+ :role => "app",
27
+ :ami => "ami-456",
28
+ :instance_type => "m1.medium",
29
+ :port => "80",
30
+ :high_threshold => "35",
31
+ :high_periods => "4",
32
+ :low_threshold => "20",
33
+ :low_periods => "2",
34
+ :max_size => "6",
35
+ :min_size => "3",
36
+ :down_adjustment => "-1",
37
+ :up_adjustment => "2"
38
+ )
39
+ end
@@ -0,0 +1,424 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+ "Description": "<%= @app.capitalize %> Stack",
4
+ "Mappings": {
5
+ "AWSInstanceType2Arch": {
6
+ "c1.medium": {
7
+ "Arch": "64"
8
+ },
9
+ "c1.xlarge": {
10
+ "Arch": "64"
11
+ },
12
+ "cc1.4xlarge": {
13
+ "Arch": "64"
14
+ },
15
+ "cc2.8xlarge": {
16
+ "Arch": "64"
17
+ },
18
+ "cg1.4xlarge": {
19
+ "Arch": "64"
20
+ },
21
+ "m1.large": {
22
+ "Arch": "64"
23
+ },
24
+ "m1.medium": {
25
+ "Arch": "64"
26
+ },
27
+ "m1.small": {
28
+ "Arch": "64"
29
+ },
30
+ "m1.xlarge": {
31
+ "Arch": "64"
32
+ },
33
+ "m2.2xlarge": {
34
+ "Arch": "64"
35
+ },
36
+ "m2.4xlarge": {
37
+ "Arch": "64"
38
+ },
39
+ "m2.xlarge": {
40
+ "Arch": "64"
41
+ },
42
+ "t1.micro": {
43
+ "Arch": "64"
44
+ }
45
+ },
46
+ "AWSRegionArch2AMI": {
47
+ "us-east-1": {
48
+ "32": "",
49
+ "64": "<%= @ami %>"
50
+ },
51
+ "us-west-1": {
52
+ "32": "",
53
+ "64": ""
54
+ }
55
+ }
56
+ },
57
+ "Outputs": {
58
+ "ELBHostname": {
59
+ "Description": "The URL of the website",
60
+ "Value": {
61
+ "Fn::Join": [
62
+ "",
63
+ [
64
+ "http://",
65
+ {
66
+ "Fn::GetAtt": [
67
+ "elb",
68
+ "DNSName"
69
+ ]
70
+ }
71
+ ]
72
+ ]
73
+ }
74
+ }
75
+ },
76
+ "Parameters": {
77
+ "Application": {
78
+ "Default": "<%= @app %>",
79
+ "Description": "Application name",
80
+ "Type": "String"
81
+ },
82
+ "Environment": {
83
+ "Default": "<%= @env %>",
84
+ "Description": "stag, prod etc",
85
+ "Type": "String"
86
+ },
87
+ "InstanceType": {
88
+ "AllowedValues": [
89
+ "t1.micro",
90
+ "m1.small",
91
+ "m1.medium",
92
+ "m1.large",
93
+ "m1.xlarge",
94
+ "m2.xlarge",
95
+ "m2.2xlarge",
96
+ "m2.4xlarge",
97
+ "c1.medium",
98
+ "c1.xlarge",
99
+ "cc1.4xlarge",
100
+ "cc2.8xlarge",
101
+ "cg1.4xlarge"
102
+ ],
103
+ "ConstraintDescription": "must be a valid EC2 instance type.",
104
+ "Default": "<%= @instance_type %>",
105
+ "Description": "WebServer EC2 instance type",
106
+ "Type": "String"
107
+ },
108
+ "KeyName": {
109
+ "Default": "default",
110
+ "Description": "The EC2 Key Pair to allow SSH access to the instances",
111
+ "Type": "String"
112
+ },
113
+ "Role": {
114
+ "Default": "<%= @role %>",
115
+ "Description": "redis, psql, app, etc",
116
+ "Type": "String"
117
+ },
118
+ "StackNumber": {
119
+ "Description": "s1, s2, s3, etc",
120
+ "Type": "String"
121
+ },
122
+ "WebServerPort": {
123
+ "Default": "<%= @port %>",
124
+ "Description": "The TCP port for the Web Server",
125
+ "Type": "Number"
126
+ }
127
+ },
128
+ "Resources": {
129
+ "CPUAlarmHigh": {
130
+ "Properties": {
131
+ "AlarmActions": [
132
+ {
133
+ "Ref": "WebServerScaleUpPolicy"
134
+ }
135
+ ],
136
+ "AlarmDescription": "Scale-up if CPU > <%= @high_threshold %>% for <%= @high_mins %> minutes",
137
+ "ComparisonOperator": "GreaterThanThreshold",
138
+ "Dimensions": [
139
+ {
140
+ "Name": "AutoScalingGroupName",
141
+ "Value": {
142
+ "Ref": "WebServerGroup"
143
+ }
144
+ }
145
+ ],
146
+ "EvaluationPeriods": "<%= @high_periods %>",
147
+ "MetricName": "CPUUtilization",
148
+ "Namespace": "AWS/EC2",
149
+ "Period": "60",
150
+ "Statistic": "Average",
151
+ "Threshold": "<%= @high_threshold %>"
152
+ },
153
+ "Type": "AWS::CloudWatch::Alarm"
154
+ },
155
+ "CPUAlarmLow": {
156
+ "Properties": {
157
+ "AlarmActions": [
158
+ {
159
+ "Ref": "WebServerScaleDownPolicy"
160
+ }
161
+ ],
162
+ "AlarmDescription": "Scale-down if CPU < <%= @low_threshold %>% for 10 minutes",
163
+ "ComparisonOperator": "LessThanThreshold",
164
+ "Dimensions": [
165
+ {
166
+ "Name": "AutoScalingGroupName",
167
+ "Value": {
168
+ "Ref": "WebServerGroup"
169
+ }
170
+ }
171
+ ],
172
+ "EvaluationPeriods": "<%= @low_periods %>",
173
+ "MetricName": "CPUUtilization",
174
+ "Namespace": "AWS/EC2",
175
+ "Period": "60",
176
+ "Statistic": "Average",
177
+ "Threshold": "<%= @low_threshold %>"
178
+ },
179
+ "Type": "AWS::CloudWatch::Alarm"
180
+ },
181
+ "HostRecord": {
182
+ "Properties": {
183
+ "Comment": "DNS name for my stack.",
184
+ "HostedZoneName": "mydomain.net.",
185
+ "Name": {
186
+ "Fn::Join": [
187
+ "",
188
+ [
189
+ {
190
+ "Ref": "AWS::StackName"
191
+ },
192
+ ".mydomain.net"
193
+ ]
194
+ ]
195
+ },
196
+ "ResourceRecords": [
197
+ {
198
+ "Fn::GetAtt": [
199
+ "elb",
200
+ "DNSName"
201
+ ]
202
+ }
203
+ ],
204
+ "TTL": "60",
205
+ "Type": "CNAME"
206
+ },
207
+ "Type": "AWS::Route53::RecordSet"
208
+ },
209
+ "LaunchConfig": {
210
+ "Properties": {
211
+ "BlockDeviceMappings": [
212
+ {
213
+ "DeviceName": "/dev/sdb",
214
+ "VirtualName": "ephemeral0"
215
+ }
216
+ ],
217
+ "ImageId": {
218
+ "Fn::FindInMap": [
219
+ "AWSRegionArch2AMI",
220
+ {
221
+ "Ref": "AWS::Region"
222
+ },
223
+ {
224
+ "Fn::FindInMap": [
225
+ "AWSInstanceType2Arch",
226
+ {
227
+ "Ref": "InstanceType"
228
+ },
229
+ "Arch"
230
+ ]
231
+ }
232
+ ]
233
+ },
234
+ "InstanceType": {
235
+ "Ref": "InstanceType"
236
+ },
237
+ "KeyName": {
238
+ "Ref": "KeyName"
239
+ },
240
+ "SecurityGroups": [
241
+ "global",
242
+ {
243
+ "Fn::Join": [
244
+ "-",
245
+ [
246
+ {
247
+ "Ref": "Environment"
248
+ },
249
+ {
250
+ "Ref": "Application"
251
+ }
252
+ ]
253
+ ]
254
+ },
255
+ {
256
+ "Ref": "ServiceSecurityGroup"
257
+ }
258
+ ],
259
+ "UserData": {
260
+ "Fn::Base64": {
261
+ "Fn::Join": [
262
+ "",
263
+ [
264
+ "#!/bin/bash -lexv\n",
265
+ "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\n",
266
+ "echo ",
267
+ {
268
+ "Ref": "AWS::StackName"
269
+ },
270
+ " > /tmp/stackname\n",
271
+ "echo \"\" >> /etc/profile.d/base.sh\n",
272
+ "'\" >> /etc/profile.d/base.sh\n",
273
+ "source /etc/profile.d/base.sh\n",
274
+ "set +e\n",
275
+ "rvm use system --default\n",
276
+ "set -e\n",
277
+ "echo 'running' > /tmp/type-of-instance\n",
278
+ "cat /proc/uptime | cut -f1 -d'.' > /tmp/time-to-boot\n"
279
+ ]
280
+ ]
281
+ }
282
+ }
283
+ },
284
+ "Type": "AWS::AutoScaling::LaunchConfiguration"
285
+ },
286
+ "ServiceSecurityGroup": {
287
+ "Properties": {
288
+ "GroupDescription": "Enable SSH access and HTTP from the load balancer only",
289
+ "SecurityGroupIngress": [
290
+ {
291
+ "CidrIp": "0.0.0.0/0",
292
+ "FromPort": "22",
293
+ "IpProtocol": "tcp",
294
+ "ToPort": "22"
295
+ },
296
+ {
297
+ "FromPort": {
298
+ "Ref": "WebServerPort"
299
+ },
300
+ "IpProtocol": "tcp",
301
+ "SourceSecurityGroupName": {
302
+ "Fn::GetAtt": [
303
+ "elb",
304
+ "SourceSecurityGroup.GroupName"
305
+ ]
306
+ },
307
+ "SourceSecurityGroupOwnerId": {
308
+ "Fn::GetAtt": [
309
+ "elb",
310
+ "SourceSecurityGroup.OwnerAlias"
311
+ ]
312
+ },
313
+ "ToPort": {
314
+ "Ref": "WebServerPort"
315
+ }
316
+ }
317
+ ]
318
+ },
319
+ "Type": "AWS::EC2::SecurityGroup"
320
+ },
321
+ "WebServerGroup": {
322
+ "Properties": {
323
+ "AvailabilityZones": [
324
+ "us-east-1a",
325
+ "us-east-1d",
326
+ "us-east-1e"
327
+ ],
328
+ "HealthCheckGracePeriod": "300",
329
+ "HealthCheckType": "ELB",
330
+ "LaunchConfigurationName": {
331
+ "Ref": "LaunchConfig"
332
+ },
333
+ "LoadBalancerNames": [
334
+ {
335
+ "Ref": "elb"
336
+ }
337
+ ],
338
+ "MaxSize": "<%= @max_size %>",
339
+ "MinSize": "<%= @min_size %>",
340
+ "NotificationConfiguration": {
341
+ "NotificationTypes": [
342
+ "autoscaling:EC2_INSTANCE_LAUNCH",
343
+ "autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
344
+ "autoscaling:EC2_INSTANCE_TERMINATE",
345
+ "autoscaling:EC2_INSTANCE_TERMINATE_ERROR"
346
+ ],
347
+ "TopicARN": [
348
+ "arn:aws:sns:us-east-1:867690557112:ops"
349
+ ]
350
+ }
351
+ },
352
+ "Type": "AWS::AutoScaling::AutoScalingGroup"
353
+ },
354
+ "WebServerScaleDownPolicy": {
355
+ "Properties": {
356
+ "AdjustmentType": "ChangeInCapacity",
357
+ "AutoScalingGroupName": {
358
+ "Ref": "WebServerGroup"
359
+ },
360
+ "Cooldown": "120",
361
+ "ScalingAdjustment": "<%= @down_adjustment %>"
362
+ },
363
+ "Type": "AWS::AutoScaling::ScalingPolicy"
364
+ },
365
+ "WebServerScaleUpPolicy": {
366
+ "Properties": {
367
+ "AdjustmentType": "ChangeInCapacity",
368
+ "AutoScalingGroupName": {
369
+ "Ref": "WebServerGroup"
370
+ },
371
+ "Cooldown": "120",
372
+ "ScalingAdjustment": "<%= @up_adjustment %>"
373
+ },
374
+ "Type": "AWS::AutoScaling::ScalingPolicy"
375
+ },
376
+ "elb": {
377
+ "Properties": {
378
+ "AvailabilityZones": [
379
+ "us-east-1a",
380
+ "us-east-1d",
381
+ "us-east-1e"
382
+ ],
383
+ "HealthCheck": {
384
+ "HealthyThreshold": "3",
385
+ "Interval": "6",
386
+ "Target": {
387
+ "Fn::Join": [
388
+ "",
389
+ [
390
+ "HTTP:",
391
+ {
392
+ "Ref": "WebServerPort"
393
+ },
394
+ "/up/elb"
395
+ ]
396
+ ]
397
+ },
398
+ "Timeout": "5",
399
+ "UnhealthyThreshold": "5"
400
+ },
401
+ "Listeners": [
402
+ {
403
+ "InstancePort": {
404
+ "Ref": "WebServerPort"
405
+ },
406
+ "LoadBalancerPort": "80",
407
+ "Protocol": "HTTP"
408
+ },
409
+ {
410
+ "InstancePort": {
411
+ "Ref": "WebServerPort"
412
+ },
413
+ "LoadBalancerPort": "443",
414
+ "PolicyNames": [],
415
+ "Protocol": "HTTPS",
416
+ "SSLCertificateId": "<%= @ssl_cert %>"
417
+ }
418
+
419
+ ]
420
+ },
421
+ "Type": "AWS::ElasticLoadBalancing::LoadBalancer"
422
+ }
423
+ }
424
+ }