lono 0.5.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +152 -23
  5. data/lib/lono.rb +1 -0
  6. data/lib/lono/cli.rb +3 -2
  7. data/lib/lono/dsl.rb +59 -12
  8. data/lib/lono/new.rb +4 -2
  9. data/lib/lono/template.rb +22 -9
  10. data/lib/lono/version.rb +1 -1
  11. data/lib/{starter_project → starter_project_json}/Gemfile +2 -1
  12. data/lib/{starter_project → starter_project_json}/Guardfile +0 -0
  13. data/lib/starter_project_json/config/lono.rb +20 -0
  14. data/lib/{starter_project → starter_project_json}/config/lono/api.rb +9 -9
  15. data/lib/{starter_project → starter_project_json}/templates/db.json.erb +1 -1
  16. data/lib/{starter_project → starter_project_json}/templates/partial/host_record.json.erb +0 -0
  17. data/lib/{starter_project → starter_project_json}/templates/partial/server.json.erb +0 -0
  18. data/lib/{starter_project → starter_project_json}/templates/user_data/app.sh.erb +1 -4
  19. data/lib/{starter_project → starter_project_json}/templates/user_data/db.sh.erb +0 -0
  20. data/lib/{starter_project → starter_project_json}/templates/user_data/db2.sh.erb +0 -0
  21. data/lib/{starter_project → starter_project_json}/templates/user_data/ruby_script.rb.erb +0 -0
  22. data/lib/{starter_project/templates/app.json.erb → starter_project_json/templates/web.json.erb} +1 -1
  23. data/lib/starter_project_yaml/Gemfile +4 -0
  24. data/lib/starter_project_yaml/Guardfile +12 -0
  25. data/lib/{starter_project → starter_project_yaml}/config/lono.rb +5 -5
  26. data/lib/starter_project_yaml/config/lono/api.rb +58 -0
  27. data/lib/starter_project_yaml/templates/db.yml.erb +148 -0
  28. data/lib/starter_project_yaml/templates/partial/host_record.yml.erb +14 -0
  29. data/lib/starter_project_yaml/templates/partial/server.yml.erb +59 -0
  30. data/lib/starter_project_yaml/templates/partial/user_data/bootstrap.sh.erb +5 -0
  31. data/lib/starter_project_yaml/templates/web.yml.erb +205 -0
  32. data/lono.gemspec +1 -1
  33. data/spec/lib/lono/dsl_spec.rb +184 -0
  34. data/spec/lib/lono/new_spec.rb +59 -0
  35. data/spec/lib/lono_spec.rb +6 -116
  36. data/spec/spec_helper.rb +1 -0
  37. metadata +42 -15
@@ -0,0 +1,5 @@
1
+ #!/bin/bash -lexv
2
+ <% stack_name = "#{@app}-#{@role}-#{@env}" -%>
3
+ exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
4
+ echo <%= stack_name %> > /tmp/stack_name
5
+ cat /proc/uptime | cut -f1 -d'.' > /tmp/time-to-boot
@@ -0,0 +1,205 @@
1
+ <% @app,@role,@env = name.sub('.yml','').split('-') -%>
2
+ ---
3
+ AWSTemplateFormatVersion: '2010-09-09'
4
+ Description: <%= @app.capitalize %> Stack
5
+ Outputs:
6
+ ELBHostname:
7
+ Description: The URL of the website
8
+ Value:
9
+ Fn::Join:
10
+ - ''
11
+ - - http://
12
+ - Fn::GetAtt:
13
+ - elb
14
+ - DNSName
15
+ Parameters:
16
+ Ami:
17
+ Default: <%= @ami %>
18
+ Description: Ami id
19
+ Type: String
20
+ Application:
21
+ Default: <%= @app %>
22
+ Description: Application name
23
+ Type: String
24
+ Environment:
25
+ Default: <%= @env %>
26
+ Description: stag, prod etc
27
+ Type: String
28
+ InstanceType:
29
+ AllowedValues:
30
+ - t1.micro
31
+ - m1.small
32
+ - m1.medium
33
+ - m1.large
34
+ - m1.xlarge
35
+ - m2.xlarge
36
+ - m2.2xlarge
37
+ - m2.4xlarge
38
+ - c1.medium
39
+ - c1.xlarge
40
+ - cc1.4xlarge
41
+ - cc2.8xlarge
42
+ - cg1.4xlarge
43
+ - t2.nano
44
+ - t2.micro
45
+ - t2.small
46
+ - t2.medium
47
+ - t2.large
48
+ - t2.xlarge
49
+ - t2.2xlarge
50
+ - m4.large
51
+ - m4.xlarge
52
+ - m4.2xlarge
53
+ - m4.4xlarge
54
+ - m4.10xlarge
55
+ - m4.16xlarge
56
+ ConstraintDescription: must be a valid EC2 instance type.
57
+ Default: <%= @instance_type %>
58
+ Description: WebServer EC2 instance type
59
+ Type: String
60
+ KeyName:
61
+ Default: default
62
+ Description: The EC2 Key Pair to allow SSH access to the instances
63
+ Type: String
64
+ Role:
65
+ Default: <%= @role %>
66
+ Description: redis, psql, app, etc
67
+ Type: String
68
+ WebServerPort:
69
+ Default: '<%= @port %>'
70
+ Description: The TCP port for the Web Server
71
+ Type: Number
72
+ Resources:
73
+ CPUAlarmHigh:
74
+ Properties:
75
+ AlarmActions:
76
+ - Ref: WebServerScaleUpPolicy
77
+ AlarmDescription: Scale-up if CPU > <%= @high_threshold %>% for <%= @high_mins %>
78
+ ComparisonOperator: GreaterThanThreshold
79
+ Dimensions:
80
+ - Name: AutoScalingGroupName
81
+ Value:
82
+ Ref: WebServerGroup
83
+ EvaluationPeriods: '<%= @high_periods %>'
84
+ MetricName: CPUUtilization
85
+ Namespace: AWS/EC2
86
+ Period: '60'
87
+ Statistic: Average
88
+ Threshold: '<%= @high_threshold %>'
89
+ Type: AWS::CloudWatch::Alarm
90
+ CPUAlarmLow:
91
+ Properties:
92
+ AlarmActions:
93
+ - Ref: WebServerScaleDownPolicy
94
+ AlarmDescription: Scale-down if CPU < <%= @low_threshold %>% for 10 minutes
95
+ ComparisonOperator: LessThanThreshold
96
+ Dimensions:
97
+ - Name: AutoScalingGroupName
98
+ Value:
99
+ Ref: WebServerGroup
100
+ EvaluationPeriods: '<%= @low_periods %>'
101
+ MetricName: CPUUtilization
102
+ Namespace: AWS/EC2
103
+ Period: '60'
104
+ Statistic: Average
105
+ Threshold: '<%= @low_threshold %>'
106
+ Type: AWS::CloudWatch::Alarm
107
+ <%= partial("host_record.yml.erb", domain: "mydomain.net") %>
108
+ LaunchConfig:
109
+ Properties:
110
+ BlockDeviceMappings:
111
+ - DeviceName: "/dev/sdb"
112
+ VirtualName: ephemeral0
113
+ ImageId: !Ref Ami
114
+ InstanceType: !Ref InstanceType
115
+ KeyName:
116
+ Ref: KeyName
117
+ SecurityGroups:
118
+ - global
119
+ - Fn::Join:
120
+ - "-"
121
+ - - Ref: Application
122
+ - Ref: Environment
123
+ - Ref: ServiceSecurityGroup
124
+ UserData:
125
+ Fn::Base64: !Sub | # No more Fn::Join needed
126
+ <%= partial("user_data/bootstrap.sh.erb", {}, indent: 10) %>
127
+ Type: AWS::AutoScaling::LaunchConfiguration
128
+ ServiceSecurityGroup:
129
+ Properties:
130
+ GroupDescription: Enable SSH access and HTTP from the load balancer only
131
+ SecurityGroupIngress:
132
+ - CidrIp: 0.0.0.0/0
133
+ FromPort: '22'
134
+ IpProtocol: tcp
135
+ ToPort: '22'
136
+ - FromPort:
137
+ Ref: WebServerPort
138
+ IpProtocol: tcp
139
+ SourceSecurityGroupName:
140
+ Fn::GetAtt:
141
+ - elb
142
+ - SourceSecurityGroup.GroupName
143
+ SourceSecurityGroupOwnerId:
144
+ Fn::GetAtt:
145
+ - elb
146
+ - SourceSecurityGroup.OwnerAlias
147
+ ToPort:
148
+ Ref: WebServerPort
149
+ Type: AWS::EC2::SecurityGroup
150
+ WebServerGroup:
151
+ Properties:
152
+ AvailabilityZones: !GetAZs ""
153
+ HealthCheckGracePeriod: '300'
154
+ HealthCheckType: ELB
155
+ LaunchConfigurationName:
156
+ Ref: LaunchConfig
157
+ LoadBalancerNames:
158
+ - Ref: elb
159
+ MaxSize: '<%= @max_size %>'
160
+ MinSize: '<%= @min_size %>'
161
+ Type: AWS::AutoScaling::AutoScalingGroup
162
+ WebServerScaleDownPolicy:
163
+ Properties:
164
+ AdjustmentType: ChangeInCapacity
165
+ AutoScalingGroupName:
166
+ Ref: WebServerGroup
167
+ Cooldown: '120'
168
+ ScalingAdjustment: "<%= @down_adjustment %>"
169
+ Type: AWS::AutoScaling::ScalingPolicy
170
+ WebServerScaleUpPolicy:
171
+ Properties:
172
+ AdjustmentType: ChangeInCapacity
173
+ AutoScalingGroupName:
174
+ Ref: WebServerGroup
175
+ Cooldown: '120'
176
+ ScalingAdjustment: '<%= @up_adjustment %>'
177
+ Type: AWS::AutoScaling::ScalingPolicy
178
+ elb:
179
+ Properties:
180
+ AvailabilityZones: !GetAZs ""
181
+ HealthCheck:
182
+ HealthyThreshold: '3'
183
+ Interval: '6'
184
+ Target:
185
+ Fn::Join:
186
+ - ''
187
+ - - 'HTTP:'
188
+ - Ref: WebServerPort
189
+ - "/up/elb"
190
+ Timeout: '5'
191
+ UnhealthyThreshold: '5'
192
+ Listeners:
193
+ - InstancePort:
194
+ Ref: WebServerPort
195
+ LoadBalancerPort: '80'
196
+ Protocol: HTTP
197
+ <% if @ssl_cert %>
198
+ - InstancePort:
199
+ Ref: WebServerPort
200
+ LoadBalancerPort: '443'
201
+ PolicyNames: []
202
+ Protocol: HTTPS
203
+ SSLCertificateId: '<%= @ssl_cert %>'
204
+ <% end %>
205
+ Type: AWS::ElasticLoadBalancing::LoadBalancer
data/lono.gemspec CHANGED
@@ -28,5 +28,5 @@ Gem::Specification.new do |gem|
28
28
  gem.add_development_dependency 'rspec'
29
29
  gem.add_development_dependency 'guard-rspec'
30
30
  gem.add_development_dependency 'guard-bundler'
31
-
31
+ gem.add_development_dependency 'byebug'
32
32
  end
@@ -0,0 +1,184 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe Lono::DSL do
4
+ before(:each) do
5
+ @project_root = File.expand_path("../../../../tmp/lono_project", __FILE__)
6
+ end
7
+ after(:each) do
8
+ FileUtils.rm_rf(@project_root) unless ENV['KEEP_TMP_PROJECT']
9
+ end
10
+
11
+ context "json starter project" do
12
+ before(:each) do
13
+ new_project = Lono::New.new(
14
+ force: true,
15
+ quiet: true,
16
+ format: 'json',
17
+ project_root: @project_root
18
+ )
19
+ new_project.run
20
+ end
21
+
22
+ it "json" do
23
+ dsl = Lono::DSL.new(
24
+ project_root: @project_root,
25
+ quiet: true
26
+ )
27
+ dsl.evaluate # run the dependent instance_eval and load_subfoler so @templates is assigned
28
+ detected_format = dsl.detect_format
29
+ expect(detected_format).to eq 'json'
30
+ end
31
+ end
32
+
33
+ context "yaml starter project" do
34
+ before(:each) do
35
+ new_project = Lono::New.new(
36
+ force: true,
37
+ quiet: true,
38
+ format: 'yaml',
39
+ project_root: @project_root
40
+ )
41
+ new_project.run
42
+ end
43
+
44
+ it "yaml" do
45
+ dsl = Lono::DSL.new(
46
+ project_root: @project_root,
47
+ quiet: true
48
+ )
49
+ dsl.evaluate # run the dependent instance_eval and load_subfoler so @templates is assigned
50
+ detected_format = dsl.detect_format
51
+ expect(detected_format).to eq 'yaml'
52
+ end
53
+ end
54
+
55
+ context "multiple format starter project" do
56
+ # it "yaml" do
57
+ # dsl = Lono::DSL.new(
58
+ # project_root: @project,
59
+ # quiet: true
60
+ # )
61
+ # detected_format = dsl.detect_format
62
+ # expect(detected_format).to eq 'yaml'
63
+ # end
64
+ end
65
+
66
+ context "json starter project" do
67
+ before(:each) do
68
+ new_project = Lono::New.new(
69
+ force: true,
70
+ quiet: true,
71
+ format: 'json',
72
+ project_root: @project_root
73
+ )
74
+ new_project.run
75
+
76
+ dsl = Lono::DSL.new(
77
+ project_root: @project_root,
78
+ quiet: true
79
+ )
80
+ dsl.run
81
+ end
82
+
83
+ it "should generate cloudformation template" do
84
+ raw = IO.read("#{@project_root}/output/api-web-prod.json")
85
+ json = JSON.load(raw)
86
+ expect(json['Description']).to eq "Api Stack"
87
+ expect(json['Mappings']['AWSRegionArch2AMI']['us-east-1']['64']).to eq 'ami-123'
88
+ end
89
+
90
+ it "should make trailing options pass to the partial helper available as instance variables" do
91
+ raw = IO.read("#{@project_root}/output/api-web-prod.json")
92
+ json = JSON.load(raw)
93
+ expect(json['Resources']['HostRecord']['Properties']['Comment']).to eq 'DNS name for mydomain.com'
94
+ end
95
+
96
+ it "should generate user data with variables" do
97
+ raw = IO.read("#{@project_root}/output/api-redis-prod.json")
98
+ json = JSON.load(raw)
99
+ expect(json['Description']).to eq "Api redis"
100
+ user_data = json['Resources']['server']['Properties']['UserData']['Fn::Base64']['Fn::Join'][1]
101
+ expect(user_data).to include("VARTEST=foo\n")
102
+ end
103
+
104
+ it "should include multiple user_data scripts" do
105
+ raw = IO.read("#{@project_root}/output/api-redis-prod.json")
106
+ json = JSON.load(raw)
107
+ expect(json['Description']).to eq "Api redis"
108
+ user_data = json['Resources']['server']['Properties']['UserData']['Fn::Base64']['Fn::Join'][1]
109
+ expect(user_data).to include("DB2=test\n")
110
+ end
111
+
112
+ it "should generate db template" do
113
+ raw = IO.read("#{@project_root}/output/api-redis-prod.json")
114
+ json = JSON.load(raw)
115
+ expect(json['Description']).to eq "Api redis"
116
+ user_data = json['Resources']['server']['Properties']['UserData']['Fn::Base64']['Fn::Join'][1]
117
+ expect(user_data).to include({"Ref" => "AWS::StackName"})
118
+ expect(user_data).to include({"Ref" => "WaitHandle"})
119
+ expect(user_data).to include({
120
+ "Fn::FindInMap" => [
121
+ "EnvironmentMapping",
122
+ "HostnamePrefix",
123
+ {"Ref" => "Environment"}
124
+ ]
125
+ })
126
+ expect(user_data).to include({
127
+ "Fn::FindInMap" => [
128
+ "MapName",
129
+ "TopLevelKey",
130
+ "SecondLevelKey"
131
+ ]
132
+ })
133
+ expect(user_data).to include({"Ref" => "DRINK"})
134
+
135
+ expect(user_data).to include({"Fn::Base64" => "value to encode"})
136
+ expect(user_data).to include({"Fn::GetAtt" => ["server", "PublicDnsName"]})
137
+ expect(user_data).to include({"Fn::GetAZs" => "AWS::Region"})
138
+ expect(user_data).to include({"Fn::Join" => [ ':', ['a','b','c']]})
139
+ expect(user_data).to include({"Fn::Select" => [ '1', ['a','b','c']]})
140
+ end
141
+
142
+ it "should transform bash script to CF template user_data" do
143
+ block = Proc.new { }
144
+ template = Lono::Template.new("foo", block)
145
+
146
+ line = 'echo {"Ref"=>"AWS::StackName"} > /tmp/stack_name ; {"Ref"=>"Ami"}'
147
+ data = template.transform(line)
148
+ expect(data).to eq ["echo ", {"Ref"=>"AWS::StackName"}, " > /tmp/stack_name ; ", {"Ref"=>"Ami"}, "\n"]
149
+
150
+ line = 'echo {"Ref"=>"AWS::StackName"} > /tmp/stack_name'
151
+ data = template.transform(line)
152
+ expect(data).to eq ["echo ", {"Ref"=>"AWS::StackName"}, " > /tmp/stack_name\n"]
153
+
154
+ line = 'echo {"Fn::FindInMap" => [ "A", "B", {"Ref"=>"AWS::StackName"} ]}'
155
+ data = template.transform(line)
156
+ expect(data).to eq ["echo ", {"Fn::FindInMap" => ["A", "B", {"Ref"=>"AWS::StackName"}]}, "\n"]
157
+
158
+ line = 'echo {"Fn::FindInMap" => [ "A", "B", {"Ref"=>"AWS::StackName"} ]} > /tmp/stack_name ; {"Ref"=>"Ami"}'
159
+ data = template.transform(line)
160
+ expect(data).to eq ["echo ", {"Fn::FindInMap" => ["A", "B", {"Ref"=>"AWS::StackName"}]}, " > /tmp/stack_name ; ", {"Ref"=>"Ami"}, "\n"]
161
+ end
162
+
163
+ it "should not transform user_data ruby scripts" do
164
+ raw = IO.read("#{@project_root}/output/api-worker-prod.json")
165
+ json = JSON.load(raw)
166
+ user_data = json['Resources']['LaunchConfig']['Properties']['UserData']['Fn::Base64']['Fn::Join'][1]
167
+ expect(user_data).to include(%Q|ec2.tags.create(ec2.instances[my_instance_id], "Name", {value: Facter.hostname})\n|)
168
+ expect(user_data).to include(%Q{find_all{ |record_set| record_set[:name] == record_name }\n})
169
+ end
170
+
171
+ it "should create parent folders for parent/db-stack.json" do
172
+ directory_created = File.exist?("#{@project_root}/output/parent")
173
+ expect(directory_created).to be true
174
+ end
175
+
176
+ it "task should generate CloudFormation templates" do
177
+ raw = IO.read("#{@project_root}/output/api-web-prod.json")
178
+ json = JSON.load(raw)
179
+ expect(json['Description']).to eq "Api Stack"
180
+ expect(json['Mappings']['AWSRegionArch2AMI']['us-east-1']['64']).to eq 'ami-123'
181
+ end
182
+ end
183
+
184
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe Lono::New do
4
+ before(:each) do
5
+ @project_root = File.expand_path("../../../../tmp/lono_project", __FILE__)
6
+ end
7
+ after(:each) do
8
+ FileUtils.rm_rf(@project_root) unless ENV['KEEP_TMP_PROJECT']
9
+ end
10
+
11
+ context "json starter project" do
12
+ before(:each) do
13
+ new_project = Lono::New.new(
14
+ force: true,
15
+ quiet: true,
16
+ format: 'json',
17
+ project_root: @project_root
18
+ )
19
+ new_project.run
20
+ end
21
+
22
+ it "should be able to lono generate" do
23
+ dsl = Lono::DSL.new(
24
+ project_root: @project_root,
25
+ quiet: true
26
+ )
27
+ dsl.run
28
+ generated = File.exist?("#{@project_root}/output/blog-web-prod.json")
29
+ expect(generated).to be true
30
+ end
31
+ end
32
+
33
+ context "yaml starter project" do
34
+ before(:each) do
35
+ new_project = Lono::New.new(
36
+ force: true,
37
+ quiet: true,
38
+ format: 'yaml',
39
+ project_root: @project_root
40
+ )
41
+ new_project.run
42
+ end
43
+
44
+ it "should be able to lono generate" do
45
+ dsl = Lono::DSL.new(
46
+ project_root: @project_root,
47
+ quiet: true
48
+ )
49
+ dsl.run
50
+ generated = File.exist?("#{@project_root}/output/blog-web-prod.yml")
51
+ expect(generated).to be true
52
+ end
53
+ end
54
+
55
+ context "multiple format starter project" do
56
+ # TODO: this should not generate anything but puts out a message to the user that the
57
+ # project needs to be either all yaml or all json format
58
+ end
59
+ end