lono 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -6
- data/lib/lono.rb +4 -1
- data/lib/lono/bashify.rb +43 -0
- data/lib/lono/cli.rb +14 -4
- data/lib/lono/task.rb +5 -0
- data/lib/lono/template.rb +107 -23
- data/lib/lono/version.rb +1 -1
- data/lib/starter_project/templates/user_data/db.sh.erb +8 -1
- data/spec/fixtures/cfn.json +369 -0
- data/spec/lib/lono_spec.rb +64 -0
- metadata +5 -2
data/README.md
CHANGED
@@ -22,9 +22,9 @@ This sets up a starter lono project with example templates.
|
|
22
22
|
$ lono generate
|
23
23
|
</pre>
|
24
24
|
|
25
|
-
This generates the templates that have been defined in config folder of the lono project.
|
25
|
+
This generates the templates that have been defined in the config folder of the lono project.
|
26
26
|
|
27
|
-
The
|
27
|
+
The starter lono template project config files looks like [this](lib/starter_project/config/lono.rb) and [this](lib/starter_project/config/lono/api.rb). Here's a snippet from one of the config files with the template call:
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
template "prod-api-app.json" do
|
@@ -54,7 +54,7 @@ The corresponding ERB template example file is [here](lib/starter_project/templa
|
|
54
54
|
|
55
55
|
## Template helper methods
|
56
56
|
|
57
|
-
There are helper methods that available in templates.
|
57
|
+
There are helper methods that are available in templates.
|
58
58
|
|
59
59
|
* partial - can be use to embed other files in a template. The partial should be placed in the templates/partial folder of the project. So:
|
60
60
|
* partial('launch_config.json.erb') -> templates/partial/launch_config.json.erb
|
@@ -75,11 +75,18 @@ Here's how you would call it in the template.
|
|
75
75
|
}
|
76
76
|
```
|
77
77
|
|
78
|
-
Within a user_data script you can
|
78
|
+
Within a user_data script you can user helper methods that correspond to Cloud Formation [Instrinic Functions](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/concept-intrinsic-functions.html). Currently, base64, find_in_map, get_att, get_azs, join, and ref are supported. Examples of their usage are found in the starter [project template](https://github.com/tongueroo/lono/blob/master/lib/starter_project/templates/user_data/db.sh.erb)
|
79
79
|
|
80
|
-
|
80
|
+
## Converting UserData scripts
|
81
81
|
|
82
|
-
|
82
|
+
You can convert UserData scripts in existing Cloud Formation Templates to a starter bash script via:
|
83
|
+
|
84
|
+
<pre>
|
85
|
+
$ lono bashify cloud_formation_template.json
|
86
|
+
$ lono bash cloud_formation_template.json # shorthand
|
87
|
+
</pre>
|
88
|
+
|
89
|
+
This is useful if you want to take an existing [Cloud Formation Template example](http://aws.amazon.com/cloudformation/aws-cloudformation-templates/) and quicklly change the UserData section into a bash script. The bashify command will generate a snippet that is meant to be copied and pasted into a bash script and used with user_data helper method. The bash script should work right off the bat as lono will transform the generated Cloud Formation object references to json objects, there's no need to manually change what is generated to the helper methods, though you can if you prefer the look of the helper methods.
|
83
90
|
|
84
91
|
## Breaking up config/lono.rb
|
85
92
|
|
data/lib/lono.rb
CHANGED
data/lib/lono/bashify.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Lono
|
2
|
+
class Bashify
|
3
|
+
def initialize(options={})
|
4
|
+
@options = options
|
5
|
+
@path = options[:path]
|
6
|
+
end
|
7
|
+
|
8
|
+
def user_data_paths(data,path="")
|
9
|
+
paths = []
|
10
|
+
paths << path
|
11
|
+
data.each do |key,value|
|
12
|
+
if value.is_a?(Hash)
|
13
|
+
paths += user_data_paths(value,"#{path}/#{key}")
|
14
|
+
else
|
15
|
+
paths += ["#{path}/#{key}"]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
paths.select {|p| p =~ /UserData/ && p =~ /Fn::Join/ }
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_user_data
|
22
|
+
raw = IO.read(@path)
|
23
|
+
json = JSON.load(raw)
|
24
|
+
|
25
|
+
user_data = json['Resources']['server']['Properties']['UserData']['Fn::Base64']['Fn::Join']
|
26
|
+
@delimiter, @data = user_data
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
raw = IO.read(@path)
|
31
|
+
json = JSON.load(raw)
|
32
|
+
paths = user_data_paths(json)
|
33
|
+
paths.each do |path|
|
34
|
+
puts "UserData script for #{path}:"
|
35
|
+
key = path.sub('/','').split("/").map {|x| "['#{x}']"}.join('')
|
36
|
+
user_data = eval("json#{key}")
|
37
|
+
delimiter = user_data[0]
|
38
|
+
script = user_data[1]
|
39
|
+
puts script.join(delimiter)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/lono/cli.rb
CHANGED
@@ -9,7 +9,7 @@ module Lono
|
|
9
9
|
method_option :project_root, :default => ".", :aliases => "-r", :desc => "project root"
|
10
10
|
method_option :quiet, :type => :boolean, :aliases => "-q", :desc => "silence the output"
|
11
11
|
def init
|
12
|
-
Lono::Task.init(options)
|
12
|
+
Lono::Task.init(options.clone)
|
13
13
|
end
|
14
14
|
|
15
15
|
desc "generate", "Generate the cloud formation templates"
|
@@ -25,8 +25,18 @@ EOL
|
|
25
25
|
method_option :project_root, :default => ".", :aliases => "-r", :desc => "project root"
|
26
26
|
method_option :quiet, :type => :boolean, :aliases => "-q", :desc => "silence the output"
|
27
27
|
def generate
|
28
|
-
Lono::Task.generate(options)
|
28
|
+
Lono::Task.generate(options.clone)
|
29
29
|
end
|
30
|
-
|
31
|
-
|
30
|
+
|
31
|
+
desc "bashify [cloudformation-path]", "Convert the UserData section of an existing Cloud Formation Template to a starter bash script that is compatiable with lono"
|
32
|
+
def bashify(path)
|
33
|
+
Lono::Task.bashify(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "version", "Prints version"
|
37
|
+
def version
|
38
|
+
puts Lono::VERSION
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
32
42
|
end
|
data/lib/lono/task.rb
CHANGED
data/lib/lono/template.rb
CHANGED
@@ -39,8 +39,7 @@ module Lono
|
|
39
39
|
template = IO.read(path)
|
40
40
|
result = ERB.new(template).result(binding)
|
41
41
|
output = []
|
42
|
-
|
43
|
-
lines.each do |line|
|
42
|
+
result.split("\n").each do |line|
|
44
43
|
output += transform(line)
|
45
44
|
end
|
46
45
|
output.to_json
|
@@ -50,25 +49,38 @@ module Lono
|
|
50
49
|
%Q|{"Ref"=>"#{name}"}|
|
51
50
|
end
|
52
51
|
|
53
|
-
# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html
|
54
52
|
def find_in_map(*args)
|
55
|
-
|
56
|
-
%Q|{"Fn::FindInMap" => [ #{args.join(',')} ]}|
|
53
|
+
%Q|{"Fn::FindInMap" => [ #{transform_array(args)} ]}|
|
57
54
|
end
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# Fn::FindInMap transform, also takes care of nested Ref transform
|
63
|
-
data = evaluate(line,/({"Fn::FindInMap" => \[ .* \]})/)
|
56
|
+
def base64(value)
|
57
|
+
%Q|{"Fn::Base64"=>"#{value}"}|
|
58
|
+
end
|
64
59
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
60
|
+
def get_att(*args)
|
61
|
+
%Q|{"Fn::GetAtt" => [ #{transform_array(args)} ]}|
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_azs(region="AWS::Region")
|
65
|
+
%Q|{"Fn::GetAZs"=>"#{region}"}|
|
66
|
+
end
|
67
|
+
|
68
|
+
def join(delimiter, values)
|
69
|
+
%Q|{"Fn::Join" => ["#{delimiter}", [ #{transform_array(values)} ]]}|
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
+
def select(index, list)
|
73
|
+
%Q|{"Fn::Select" => ["#{index}", [ #{transform_array(list)} ]]}|
|
74
|
+
end
|
75
|
+
|
76
|
+
def transform_array(arr)
|
77
|
+
arr.map! {|x| x =~ /=>/ ? x : x.inspect }
|
78
|
+
arr.join(',')
|
79
|
+
end
|
80
|
+
|
81
|
+
# transform each line of bash script to array with cloud formation template objects
|
82
|
+
def transform(data)
|
83
|
+
data = evaluate(data)
|
72
84
|
if data[-1].is_a?(String)
|
73
85
|
data[0..-2] + ["#{data[-1]}\n"]
|
74
86
|
else
|
@@ -76,13 +88,85 @@ module Lono
|
|
76
88
|
end
|
77
89
|
end
|
78
90
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
91
|
+
# Input:
|
92
|
+
# String
|
93
|
+
# Output:
|
94
|
+
# Array of parse positions
|
95
|
+
#
|
96
|
+
# The positions indicate when the brackets start and close.
|
97
|
+
# Handles nested brackets.
|
98
|
+
def bracket_positions(line)
|
99
|
+
positions,pair,count = [],[],0
|
100
|
+
line.split('').each_with_index do |char,i|
|
101
|
+
if char == '{'
|
102
|
+
count += 1
|
103
|
+
pair << i if count == 1
|
104
|
+
next
|
105
|
+
end
|
106
|
+
|
107
|
+
if char == '}'
|
108
|
+
count -= 1
|
109
|
+
if count == 0
|
110
|
+
pair << i
|
111
|
+
positions << pair
|
112
|
+
pair = []
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
positions
|
117
|
+
end
|
118
|
+
|
119
|
+
# Input:
|
120
|
+
# Array - bracket_positions
|
121
|
+
# Ouput:
|
122
|
+
# Array - positions that can be use to determine what to parse
|
123
|
+
def parse_positions(line)
|
124
|
+
positions = bracket_positions(line)
|
125
|
+
# add 1 to the element in the position pair to make parsing easier in decompose
|
126
|
+
positions.map {|pair| [pair[0],pair[1]+1]}.flatten
|
127
|
+
end
|
128
|
+
|
129
|
+
# Input
|
130
|
+
# String line of code to decompose into chunks, some can be transformed into objects
|
131
|
+
# Output
|
132
|
+
# Array of strings, some can be transformed into objects
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
# line = 'a{b}c{d{d}d}e' # nested brackets
|
136
|
+
# template.decompose(line).should == ['a','{b}','c','{d{d}d}','e']
|
137
|
+
def decompose(line)
|
138
|
+
positions = parse_positions(line)
|
139
|
+
return [line] if positions.empty?
|
140
|
+
|
141
|
+
result = []
|
142
|
+
str = ''
|
143
|
+
last_index = line.size - 1
|
144
|
+
parse_position = positions.shift
|
145
|
+
|
146
|
+
line.split('').each_with_index do |char,current_i|
|
147
|
+
# the current item's creation will end when
|
148
|
+
# the next item's index is reached
|
149
|
+
# or the end of the line is reached
|
150
|
+
str << char
|
151
|
+
next_i = current_i + 1
|
152
|
+
end_of_item = next_i == parse_position
|
153
|
+
end_of_line = current_i == last_index
|
154
|
+
if end_of_item or end_of_line
|
155
|
+
parse_position = positions.shift
|
156
|
+
result << str
|
157
|
+
str = ''
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
result
|
162
|
+
end
|
163
|
+
|
164
|
+
def recompose(decomposition)
|
165
|
+
decomposition.map { |s| (s =~ /^{/ && s =~ /=>/) ? eval(s) : s }
|
166
|
+
end
|
167
|
+
|
168
|
+
def evaluate(line)
|
169
|
+
recompose(decompose(line))
|
86
170
|
end
|
87
171
|
end
|
88
172
|
end
|
data/lib/lono/version.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
#!/bin/bash -lexv
|
2
2
|
exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
|
3
3
|
|
4
|
-
|
4
|
+
FIND_IN_MAP=<%= find_in_map("MapName", "TopLevelKey", "SecondLevelKey") %>
|
5
5
|
HOSTNAME_PREFIX=<%= find_in_map("EnvironmentMapping", "HostnamePrefix", ref("Environment")) %>
|
6
|
+
BAR=<%= ref("DRINK") %> ; MORE=<%= find_in_map("MapName", "TopLevelKey", "SecondLevelKey") %>
|
7
|
+
|
8
|
+
BASE64=<%= base64("value to encode") %>
|
9
|
+
GET_ATT=<%= get_att("server", "PublicDnsName") %>
|
10
|
+
GET_AZS=<%= get_azs %>
|
11
|
+
JOIN=<%= join(":", ['a','b','c']) %>
|
12
|
+
SELECT=<%= select("1", ['a','b','c']) %>
|
6
13
|
|
7
14
|
echo <%= ref("AWS::StackName") %> > /tmp/stack_name
|
8
15
|
# Helper function
|
@@ -0,0 +1,369 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
3
|
+
"Description": "Api redis",
|
4
|
+
"Outputs": {
|
5
|
+
"DBHostname": {
|
6
|
+
"Description": "Hostname for Redis",
|
7
|
+
"Value": {
|
8
|
+
"Fn::Join": [
|
9
|
+
"",
|
10
|
+
[
|
11
|
+
"",
|
12
|
+
{
|
13
|
+
"Fn::GetAtt": [
|
14
|
+
"server",
|
15
|
+
"PublicDnsName"
|
16
|
+
]
|
17
|
+
}
|
18
|
+
]
|
19
|
+
]
|
20
|
+
}
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"Parameters": {
|
24
|
+
"Ami": {
|
25
|
+
"Default": "ami-456",
|
26
|
+
"Description": "deploy ami",
|
27
|
+
"Type": "String"
|
28
|
+
},
|
29
|
+
"Application": {
|
30
|
+
"Default": "api",
|
31
|
+
"Description": "foo, bar, etc",
|
32
|
+
"Type": "String"
|
33
|
+
},
|
34
|
+
"Environment": {
|
35
|
+
"Default": "prod",
|
36
|
+
"Description": "stag, prod etc",
|
37
|
+
"Type": "String"
|
38
|
+
},
|
39
|
+
"InstanceType": {
|
40
|
+
"AllowedValues": [
|
41
|
+
"t1.micro",
|
42
|
+
"m1.small",
|
43
|
+
"m1.medium",
|
44
|
+
"m1.large",
|
45
|
+
"m1.xlarge",
|
46
|
+
"m2.xlarge",
|
47
|
+
"m2.2xlarge",
|
48
|
+
"m2.4xlarge",
|
49
|
+
"c1.medium",
|
50
|
+
"c1.xlarge",
|
51
|
+
"cc1.4xlarge",
|
52
|
+
"cc2.8xlarge",
|
53
|
+
"cg1.4xlarge"
|
54
|
+
],
|
55
|
+
"ConstraintDescription": "must be a valid EC2 instance type.",
|
56
|
+
"Default": "m1.small",
|
57
|
+
"Description": "server EC2 instance type",
|
58
|
+
"Type": "String"
|
59
|
+
},
|
60
|
+
"KeyName": {
|
61
|
+
"AllowedPattern": "[-_ a-zA-Z0-9]*",
|
62
|
+
"ConstraintDescription": "can contain only alphanumeric characters, spaces, dashes and underscores.",
|
63
|
+
"Default": "default",
|
64
|
+
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
|
65
|
+
"MaxLength": "64",
|
66
|
+
"MinLength": "1",
|
67
|
+
"Type": "String"
|
68
|
+
},
|
69
|
+
"Role": {
|
70
|
+
"Default": "redis",
|
71
|
+
"Description": "redis, psql, app, etc",
|
72
|
+
"Type": "String"
|
73
|
+
},
|
74
|
+
"StackNumber": {
|
75
|
+
"Default": "",
|
76
|
+
"Description": "s1, s2, s3, etc",
|
77
|
+
"Type": "String"
|
78
|
+
},
|
79
|
+
"VolumeSize": {
|
80
|
+
"ConstraintDescription": "must be between 5 and 1024 Gb.",
|
81
|
+
"Default": "20",
|
82
|
+
"Description": "Size of Volume",
|
83
|
+
"MaxValue": "1024",
|
84
|
+
"MinValue": "5",
|
85
|
+
"Type": "Number"
|
86
|
+
}
|
87
|
+
},
|
88
|
+
"Resources": {
|
89
|
+
"CfnUser": {
|
90
|
+
"Properties": {
|
91
|
+
"Path": "/",
|
92
|
+
"Policies": [
|
93
|
+
{
|
94
|
+
"PolicyDocument": {
|
95
|
+
"Statement": [
|
96
|
+
{
|
97
|
+
"Action": "cloudformation:DescribeStackResource",
|
98
|
+
"Effect": "Allow",
|
99
|
+
"Resource": "*"
|
100
|
+
}
|
101
|
+
]
|
102
|
+
},
|
103
|
+
"PolicyName": "root"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
},
|
107
|
+
"Type": "AWS::IAM::User"
|
108
|
+
},
|
109
|
+
"DataVolume": {
|
110
|
+
"DeletionPolicy": "Snapshot",
|
111
|
+
"Properties": {
|
112
|
+
"AvailabilityZone": {
|
113
|
+
"Fn::GetAtt": [
|
114
|
+
"server",
|
115
|
+
"AvailabilityZone"
|
116
|
+
]
|
117
|
+
},
|
118
|
+
"Size": {
|
119
|
+
"Ref": "VolumeSize"
|
120
|
+
},
|
121
|
+
"Tags": [
|
122
|
+
{
|
123
|
+
"Key": "Usage",
|
124
|
+
"Value": "Data Volume"
|
125
|
+
}
|
126
|
+
]
|
127
|
+
},
|
128
|
+
"Type": "AWS::EC2::Volume"
|
129
|
+
},
|
130
|
+
"HostKeys": {
|
131
|
+
"Properties": {
|
132
|
+
"UserName": {
|
133
|
+
"Ref": "CfnUser"
|
134
|
+
}
|
135
|
+
},
|
136
|
+
"Type": "AWS::IAM::AccessKey"
|
137
|
+
},
|
138
|
+
"HostRecord": {
|
139
|
+
"Properties": {
|
140
|
+
"Comment": "DNS name for my stack.",
|
141
|
+
"HostedZoneName": ".mydomain.net.",
|
142
|
+
"Name": {
|
143
|
+
"Fn::Join": [
|
144
|
+
"",
|
145
|
+
[
|
146
|
+
{
|
147
|
+
"Ref": "AWS::StackName"
|
148
|
+
},
|
149
|
+
".mydomain.net."
|
150
|
+
]
|
151
|
+
]
|
152
|
+
},
|
153
|
+
"ResourceRecords": [
|
154
|
+
{
|
155
|
+
"Fn::GetAtt": [
|
156
|
+
"server",
|
157
|
+
"PublicDnsName"
|
158
|
+
]
|
159
|
+
}
|
160
|
+
],
|
161
|
+
"TTL": "60",
|
162
|
+
"Type": "CNAME"
|
163
|
+
},
|
164
|
+
"Type": "AWS::Route53::RecordSet"
|
165
|
+
},
|
166
|
+
"MountPoint": {
|
167
|
+
"Properties": {
|
168
|
+
"Device": "/dev/sdf",
|
169
|
+
"InstanceId": {
|
170
|
+
"Ref": "server"
|
171
|
+
},
|
172
|
+
"VolumeId": {
|
173
|
+
"Ref": "DataVolume"
|
174
|
+
}
|
175
|
+
},
|
176
|
+
"Type": "AWS::EC2::VolumeAttachment"
|
177
|
+
},
|
178
|
+
"ServiceSecurityGroup": {
|
179
|
+
"Properties": {
|
180
|
+
"GroupDescription": "Enable SSH access.",
|
181
|
+
"SecurityGroupIngress": [
|
182
|
+
{
|
183
|
+
"CidrIp": "0.0.0.0/0",
|
184
|
+
"FromPort": "22",
|
185
|
+
"IpProtocol": "tcp",
|
186
|
+
"ToPort": "22"
|
187
|
+
}
|
188
|
+
]
|
189
|
+
},
|
190
|
+
"Type": "AWS::EC2::SecurityGroup"
|
191
|
+
},
|
192
|
+
"WaitCondition": {
|
193
|
+
"DependsOn": "MountPoint",
|
194
|
+
"Metadata": {
|
195
|
+
"Comment1": "Note that the WaitCondition is dependent on the volume mount point allowing the volume to be created and attached to the EC2 instance",
|
196
|
+
"Comment2": "The instance bootstrap script waits for the volume to be attached to the instance prior to signalling completion."
|
197
|
+
},
|
198
|
+
"Properties": {
|
199
|
+
"Handle": {
|
200
|
+
"Ref": "WaitHandle"
|
201
|
+
},
|
202
|
+
"Timeout": "3000"
|
203
|
+
},
|
204
|
+
"Type": "AWS::CloudFormation::WaitCondition"
|
205
|
+
},
|
206
|
+
"WaitHandle": {
|
207
|
+
"Type": "AWS::CloudFormation::WaitConditionHandle"
|
208
|
+
},
|
209
|
+
"server": {
|
210
|
+
"Properties": {
|
211
|
+
"AvailabilityZone": "us-east-1e",
|
212
|
+
"ImageId": {
|
213
|
+
"Ref": "Ami"
|
214
|
+
},
|
215
|
+
"InstanceType": {
|
216
|
+
"Ref": "InstanceType"
|
217
|
+
},
|
218
|
+
"KeyName": {
|
219
|
+
"Ref": "KeyName"
|
220
|
+
},
|
221
|
+
"SecurityGroups": [
|
222
|
+
"global",
|
223
|
+
{
|
224
|
+
"Fn::Join": [
|
225
|
+
"-",
|
226
|
+
[
|
227
|
+
{
|
228
|
+
"Ref": "Environment"
|
229
|
+
},
|
230
|
+
{
|
231
|
+
"Ref": "Application"
|
232
|
+
}
|
233
|
+
]
|
234
|
+
]
|
235
|
+
},
|
236
|
+
{
|
237
|
+
"Ref": "ServiceSecurityGroup"
|
238
|
+
}
|
239
|
+
],
|
240
|
+
"UserData": {
|
241
|
+
"Fn::Base64": {
|
242
|
+
"Fn::Join": [
|
243
|
+
"",
|
244
|
+
[
|
245
|
+
"#!/bin/bash -lexv\n",
|
246
|
+
"exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\n",
|
247
|
+
"\n",
|
248
|
+
"FIND_IN_MAP=",
|
249
|
+
{
|
250
|
+
"Fn::FindInMap": [
|
251
|
+
"MapName",
|
252
|
+
"TopLevelKey",
|
253
|
+
"SecondLevelKey"
|
254
|
+
]
|
255
|
+
},
|
256
|
+
"\n",
|
257
|
+
"HOSTNAME_PREFIX=",
|
258
|
+
{
|
259
|
+
"Fn::FindInMap": [
|
260
|
+
"EnvironmentMapping",
|
261
|
+
"HostnamePrefix",
|
262
|
+
{
|
263
|
+
"Ref": "Environment"
|
264
|
+
}
|
265
|
+
]
|
266
|
+
},
|
267
|
+
"\n",
|
268
|
+
"BAR=",
|
269
|
+
{
|
270
|
+
"Ref": "DRINK"
|
271
|
+
},
|
272
|
+
" ; MORE=",
|
273
|
+
{
|
274
|
+
"Fn::FindInMap": [
|
275
|
+
"MapName",
|
276
|
+
"TopLevelKey",
|
277
|
+
"SecondLevelKey"
|
278
|
+
]
|
279
|
+
},
|
280
|
+
"\n",
|
281
|
+
"\n",
|
282
|
+
"BASE64=",
|
283
|
+
{
|
284
|
+
"Fn::Base64": "value to encode"
|
285
|
+
},
|
286
|
+
"\n",
|
287
|
+
"GET_ATT=",
|
288
|
+
{
|
289
|
+
"Fn::GetAtt": [
|
290
|
+
"server",
|
291
|
+
"PublicDnsName"
|
292
|
+
]
|
293
|
+
},
|
294
|
+
"\n",
|
295
|
+
"GET_AZS=",
|
296
|
+
{
|
297
|
+
"Fn::GetAZs": "AWS::Region"
|
298
|
+
},
|
299
|
+
"\n",
|
300
|
+
"JOIN=",
|
301
|
+
{
|
302
|
+
"Fn::Join": [
|
303
|
+
":",
|
304
|
+
[
|
305
|
+
"a",
|
306
|
+
"b",
|
307
|
+
"c"
|
308
|
+
]
|
309
|
+
]
|
310
|
+
},
|
311
|
+
"\n",
|
312
|
+
"SELECT=",
|
313
|
+
{
|
314
|
+
"Fn::Select": [
|
315
|
+
"1",
|
316
|
+
[
|
317
|
+
"a",
|
318
|
+
"b",
|
319
|
+
"c"
|
320
|
+
]
|
321
|
+
]
|
322
|
+
},
|
323
|
+
"\n",
|
324
|
+
"\n",
|
325
|
+
"echo ",
|
326
|
+
{
|
327
|
+
"Ref": "AWS::StackName"
|
328
|
+
},
|
329
|
+
" > /tmp/stack_name\n",
|
330
|
+
"# Helper function\n",
|
331
|
+
"function error_exit\n",
|
332
|
+
"{\n",
|
333
|
+
" /usr/local/bin/cfn-signal -e 1 -r \"$1\" '",
|
334
|
+
{
|
335
|
+
"Ref": "WaitHandle"
|
336
|
+
},
|
337
|
+
"'\n",
|
338
|
+
"exit 1\n",
|
339
|
+
"}\n",
|
340
|
+
"# Wait for the EBS volume to show up\n",
|
341
|
+
"while [ ! -e /dev/xvdf ]; do echo Waiting for EBS volume to attach; sleep 1; done\n",
|
342
|
+
"/bin/mkdir /media/redis\n",
|
343
|
+
"/sbin/mkfs -t ext4 /dev/xvdf\n",
|
344
|
+
"echo \"/dev/xvdf /media/redis auto defaults 0 0\" >> /etc/fstab\n",
|
345
|
+
"/bin/mount /media/redis\n",
|
346
|
+
"/usr/bin/redis-cli shutdown\n",
|
347
|
+
"sleep 10\n",
|
348
|
+
"mv /var/lib/redis/* /media/redis/\n",
|
349
|
+
"rm -r /var/lib/redis\n",
|
350
|
+
"ln -s /media/redis /var/lib/redis\n",
|
351
|
+
"chown -R redis:redis /var/lib/redis\n",
|
352
|
+
"chown -R redis:redis /media/redis\n",
|
353
|
+
"/usr/bin/redis-server\n",
|
354
|
+
"# If all is well so signal success\n",
|
355
|
+
"/usr/local/bin/cfn-signal -e $? -r \"Ready to rock\" '",
|
356
|
+
{
|
357
|
+
"Ref": "WaitHandle"
|
358
|
+
},
|
359
|
+
"'\n",
|
360
|
+
"cat /proc/uptime | cut -f1 -d'.' > /tmp/time-to-boot\n"
|
361
|
+
]
|
362
|
+
]
|
363
|
+
}
|
364
|
+
}
|
365
|
+
},
|
366
|
+
"Type": "AWS::EC2::Instance"
|
367
|
+
}
|
368
|
+
}
|
369
|
+
}
|
data/spec/lib/lono_spec.rb
CHANGED
@@ -11,6 +11,55 @@ describe Lono do
|
|
11
11
|
FileUtils.rm_rf(@project)
|
12
12
|
end
|
13
13
|
|
14
|
+
describe "bashify" do
|
15
|
+
it "should convert cfn user_data to bash script" do
|
16
|
+
path = "#{$root}/spec/fixtures/cfn.json"
|
17
|
+
out = execute("./bin/lono bashify #{path}")
|
18
|
+
out.should match /bash -lexv/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "parsing" do
|
23
|
+
it "should transform bash script into json array with cloud formation objects" do
|
24
|
+
block = Proc.new { }
|
25
|
+
template = Lono::Template.new("foo", block)
|
26
|
+
|
27
|
+
line = '0{2345}7'
|
28
|
+
template.bracket_positions(line).should == [[1,6]]
|
29
|
+
line = '0{2}4{6}' # more than one bracket
|
30
|
+
template.bracket_positions(line).should == [[1,3],[5,7]]
|
31
|
+
line = '0{2}4{6{8}0}2' # nested brackets
|
32
|
+
template.bracket_positions(line).should == [[1,3],[5,11]]
|
33
|
+
|
34
|
+
line = '0{2=>5}7'
|
35
|
+
template.parse_positions(line).should == [1,7]
|
36
|
+
line = '0{2=>5}4{6=>{8=>9}}2' # nested brackets
|
37
|
+
template.parse_positions(line).should == [1, 7, 8, 19]
|
38
|
+
line = '0{2=>5}4{' # nested brackets
|
39
|
+
template.parse_positions(line).should == [1, 7]
|
40
|
+
|
41
|
+
line = '{'
|
42
|
+
template.decompose(line).should == ['{']
|
43
|
+
|
44
|
+
line = 'a{"foo"=>"bar"}h'
|
45
|
+
template.decompose(line).should == ['a','{"foo"=>"bar"}','h']
|
46
|
+
line = 'a{"foo"=>"bar"}c{"dog"=>{"cat"=>"mouse"}}e' # nested brackets
|
47
|
+
template.decompose(line).should == ['a','{"foo"=>"bar"}','c','{"dog"=>{"cat"=>"mouse"}}','e']
|
48
|
+
|
49
|
+
line = 'test{"hello"=>"world"}me' # nested brackets
|
50
|
+
decomposition = template.decompose(line)
|
51
|
+
result = template.recompose(decomposition)
|
52
|
+
result.should == ["test", {"hello" => "world"}, "me"]
|
53
|
+
|
54
|
+
line = 'test{"hello"=>"world"}me'
|
55
|
+
template.transform(line).should == ["test", {"hello" => "world"}, "me\n"]
|
56
|
+
line = '{"hello"=>"world"}'
|
57
|
+
template.transform(line).should == [{"hello" => "world"}, "\n"]
|
58
|
+
line = '{'
|
59
|
+
template.transform(line).should == ["{\n"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
14
63
|
describe "ruby specs" do
|
15
64
|
before(:each) do
|
16
65
|
@dsl = Lono::DSL.new(
|
@@ -48,6 +97,13 @@ describe Lono do
|
|
48
97
|
"SecondLevelKey"
|
49
98
|
]
|
50
99
|
})
|
100
|
+
user_data.should include({"Ref" => "DRINK"})
|
101
|
+
|
102
|
+
user_data.should include({"Fn::Base64" => "value to encode"})
|
103
|
+
user_data.should include({"Fn::GetAtt" => ["server", "PublicDnsName"]})
|
104
|
+
user_data.should include({"Fn::GetAZs" => "AWS::Region"})
|
105
|
+
user_data.should include({"Fn::Join" => [ ':', ['a','b','c']]})
|
106
|
+
user_data.should include({"Fn::Select" => [ '1', ['a','b','c']]})
|
51
107
|
end
|
52
108
|
|
53
109
|
it "should transform bash script to CF template user_data" do
|
@@ -61,6 +117,14 @@ describe Lono do
|
|
61
117
|
line = 'echo {"Ref"=>"AWS::StackName"} > /tmp/stack_name'
|
62
118
|
data = template.transform(line)
|
63
119
|
data.should == ["echo ", {"Ref"=>"AWS::StackName"}, " > /tmp/stack_name\n"]
|
120
|
+
|
121
|
+
line = 'echo {"Fn::FindInMap" => [ "A", "B", {"Ref"=>"AWS::StackName"} ]}'
|
122
|
+
data = template.transform(line)
|
123
|
+
data.should == ["echo ", {"Fn::FindInMap" => ["A", "B", {"Ref"=>"AWS::StackName"}]}, "\n"]
|
124
|
+
|
125
|
+
line = 'echo {"Fn::FindInMap" => [ "A", "B", {"Ref"=>"AWS::StackName"} ]} > /tmp/stack_name ; {"Ref"=>"Ami"}'
|
126
|
+
data = template.transform(line)
|
127
|
+
data.should == ["echo ", {"Fn::FindInMap" => ["A", "B", {"Ref"=>"AWS::StackName"}]}, " > /tmp/stack_name ; ", {"Ref"=>"Ami"}, "\n"]
|
64
128
|
end
|
65
129
|
|
66
130
|
it "task should generate cloud formation templates" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lono
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06
|
12
|
+
date: 2013-07-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -207,6 +207,7 @@ files:
|
|
207
207
|
- bin/lono
|
208
208
|
- lib/ext/hash.rb
|
209
209
|
- lib/lono.rb
|
210
|
+
- lib/lono/bashify.rb
|
210
211
|
- lib/lono/cli.rb
|
211
212
|
- lib/lono/dsl.rb
|
212
213
|
- lib/lono/task.rb
|
@@ -222,6 +223,7 @@ files:
|
|
222
223
|
- lib/starter_project/templates/user_data/app.sh.erb
|
223
224
|
- lib/starter_project/templates/user_data/db.sh.erb
|
224
225
|
- lono.gemspec
|
226
|
+
- spec/fixtures/cfn.json
|
225
227
|
- spec/lib/lono_spec.rb
|
226
228
|
- spec/spec_helper.rb
|
227
229
|
homepage: http://github.com/tongueroo/lono
|
@@ -250,5 +252,6 @@ specification_version: 3
|
|
250
252
|
summary: Lono is a Cloud Formation Template ruby generator. Lono generates Cloud
|
251
253
|
Formation templates based on ERB templates.
|
252
254
|
test_files:
|
255
|
+
- spec/fixtures/cfn.json
|
253
256
|
- spec/lib/lono_spec.rb
|
254
257
|
- spec/spec_helper.rb
|