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 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 one of starter lono config files looks like this:
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 call another helper method called ref.
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
- * ref - can be use to reference other parameter or resource value within the cloud formation template. An [example](lib/starter_project/templates/user_data/db.sh.erb) is in the [starter_project](lib/starter_project).
80
+ ## Converting UserData scripts
81
81
 
82
- * find_in_map - can be use to call the find_in_map function within the cloud formation template. An [example](lib/starter_project/templates/user_data/db.sh.erb) is in the [starter_project](lib/starter_project).
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
 
@@ -1,8 +1,11 @@
1
1
  require 'json'
2
+ require 'pp'
2
3
 
3
4
  $:.unshift File.dirname(__FILE__)
4
5
  require 'ext/hash'
6
+ require 'lono/version'
5
7
  require 'lono/cli'
6
8
  require 'lono/task'
7
9
  require 'lono/template'
8
- require 'lono/dsl'
10
+ require 'lono/dsl'
11
+ require 'lono/bashify'
@@ -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
@@ -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
- end
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
@@ -32,5 +32,10 @@ module Lono
32
32
  def generate
33
33
  @dsl.run(@options)
34
34
  end
35
+
36
+ def self.bashify(path)
37
+ @bashify = Lono::Bashify.new(:path => path)
38
+ @bashify.run
39
+ end
35
40
  end
36
41
  end
@@ -39,8 +39,7 @@ module Lono
39
39
  template = IO.read(path)
40
40
  result = ERB.new(template).result(binding)
41
41
  output = []
42
- lines = result.split("\n")
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
- args.map! {|x| x =~ /=>/ ? x : x.inspect }
56
- %Q|{"Fn::FindInMap" => [ #{args.join(',')} ]}|
53
+ %Q|{"Fn::FindInMap" => [ #{transform_array(args)} ]}|
57
54
  end
58
55
 
59
- # transform each line of the bash script into a UserData compatiable with CF template.
60
- # any {"Ref"=>"..."} string get turned into CF Hash elements
61
- def transform(line)
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
- # Ref transform
66
- already_transformed = data.size > 1
67
- unless already_transformed
68
- data = evaluate(line,/({"Ref"=>".*?"})/)
69
- end
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
- # add newline at the end
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
- # if regex found in line, the match is eval into ruby code
80
- # returns array of evaluated items
81
- # if regex pattern not found
82
- # returns array with original line
83
- def evaluate(line, regex)
84
- data = line.split(regex)
85
- data.map {|l| l.is_a?(String) && l.match(regex) ? eval(l) : l }
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
@@ -1,3 +1,3 @@
1
1
  module Lono
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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
- FINDINMAP_TEST=<%= find_in_map("MapName", "TopLevelKey", "SecondLevelKey") %>
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
+ }
@@ -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.2.4
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-26 00:00:00.000000000 Z
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