lono 0.5.2 → 1.0.0

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.
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