cloudformation-ruby-dsl 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3174d42bb251437f17975b141976100ffbe490ea
4
- data.tar.gz: 8dbaf407a4062a3a4009f926e38e355f08c68f79
3
+ metadata.gz: b96d9566c8f7eb64af7c88c4d809d6c6b6cd79ac
4
+ data.tar.gz: d43e2ce163a5047f8aeecd15cf936bdf422bfff8
5
5
  SHA512:
6
- metadata.gz: 354b62ddb73631ef94cc0cc85ce78bbb9857b6ef8ad4bf1e2693306417d8a7500d0c26ff34b95e99a990cec1811d1a16a7b45f9f6cf58528bd92eecd69b910e7
7
- data.tar.gz: e4452d3db3bc6eb9d597be62b6b3c6552ced7066e7028f8b4be91f305f36d736e586f9777fd4fbb8a921c2c6c4031398de62b94f8c0a26dbaf1ffa64e9253eaa
6
+ metadata.gz: 457204dfb740fbccc51371e4017006bab15e9b03e3ced9bace517850f0494008dc653171b60626c02addc13fe191c25c5601cd466a469c16d0381c4ae12a67e6
7
+ data.tar.gz: 12a0a10691d43c267fb2172a9a0ba7b703f4f16ecebd95f856a1028ac4b5924def354dae0f10d1ea60d6e27371a33e31b035fd99244b37142ea294b978439c53
data/README.md CHANGED
@@ -67,6 +67,7 @@ Add the named object to the appropriate collection.
67
67
  - `condition(name, conditions)`
68
68
  - `resource(name, options)`
69
69
  - `output(name, options)`
70
+ - `metadata(object)`
70
71
 
71
72
  ### CloudFormation Function Calls
72
73
 
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ rescue LoadError
7
+ end
8
+
9
+ task :default => :spec
@@ -113,6 +113,8 @@ def pprint_cfn_template(tpl)
113
113
  puts
114
114
  tpl.each do |section, v|
115
115
  case section
116
+ when 'Metadata'
117
+ v.each { |name, options| pprint_cfn_section 'metadata', name, options }
116
118
  when 'Parameters'
117
119
  v.each { |name, options| pprint_cfn_section 'parameter', name, options }
118
120
  when 'Mappings'
@@ -39,4 +39,7 @@ Gem::Specification.new do |gem|
39
39
  gem.add_runtime_dependency 'diffy'
40
40
  gem.add_runtime_dependency 'highline'
41
41
  gem.add_runtime_dependency 'rake'
42
+
43
+ gem.add_development_dependency 'rspec'
44
+ gem.add_development_dependency 'pry'
42
45
  end
data/docs/Contributing.md CHANGED
@@ -8,7 +8,8 @@ To get started:
8
8
 
9
9
  - fork this project on github
10
10
  - create a new branch named after the change you want to make; i.e., `git checkout -b mynewfeature`
11
- - make your changes and commit them
11
+ - make your changes (including tests) and commit them
12
+ - run the tests to make sure you haven't broken anything: ```rspec```
12
13
  - send a pull request to this project from your fork/branch
13
14
 
14
15
  Once you've sent your pull request, one of the project collaborators will review it and provide feedback. Please accept this commentary as constructive! It is intended as such.
@@ -18,4 +19,4 @@ Once you've sent your pull request, one of the project collaborators will review
18
19
  We're opinionated about git. Don't be surprised if we ask you to update your pull request to meet the following standards.
19
20
 
20
21
  - rebase+squash your branch into a single commit. For clean git history, we'd prefer we merged in just 1 commit that contains the entire set of changes.
21
- - don't write commit messages longer than 50 characters. See [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for some examples of how to achieve this, and why.
22
+ - don't write commit messages longer than 50 characters. See [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for some examples of how to achieve this, and why.
@@ -23,6 +23,26 @@ require 'cloudformation-ruby-dsl/table'
23
23
 
24
24
  template do
25
25
 
26
+ # Metadata may be embedded into the stack, as per http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html
27
+ # The below definition produces CloudFormation interface metadata.
28
+ metadata 'AWS::CloudFormation::Interface', {
29
+ :ParameterGroups => [
30
+ {
31
+ :Label => { :default => 'Instance options' },
32
+ :Parameters => [ 'InstanceType', 'ImageId', 'KeyPairName' ]
33
+ },
34
+ {
35
+ :Label => { :default => 'Other options' },
36
+ :Parameters => [ 'Label', 'EmailAddress' ]
37
+ }
38
+ ],
39
+ :ParameterLabels => {
40
+ :EmailAddress => {
41
+ :default => "We value your privacy!"
42
+ }
43
+ }
44
+ }
45
+
26
46
  parameter 'Label',
27
47
  :Description => 'The label to apply to the servers.',
28
48
  :Type => 'String',
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'cloudformation-ruby-dsl/cfntemplate'
4
+
5
+ template = template do
6
+ resource "HelloBucket", :Type => "AWS::S3::Bucket"
7
+ end
8
+
9
+ template.exec!
@@ -37,7 +37,7 @@ class AwsCfn
37
37
  def initialize(args)
38
38
  Aws.config[:region] = args[:region] if args.key?(:region)
39
39
  # Profile definition was replaced with environment variables
40
- if args.key?(:aws_profile)
40
+ if args.key?(:aws_profile) && !(args[:aws_profile].nil? || args[:aws_profile].empty?)
41
41
  ENV['AWS_PROFILE'] = args[:aws_profile]
42
42
  ENV['AWS_ACCESS_KEY'] = nil
43
43
  ENV['AWS_ACCESS_KEY_ID'] = nil
@@ -78,26 +78,32 @@ end
78
78
 
79
79
  # Parse command-line arguments and return the parameters and region
80
80
  def parse_args
81
- stack_name = nil
82
- parameters = {}
83
- region = default_region
84
- profile = nil
85
- nopretty = false
81
+ args = {
82
+ :stack_name => nil,
83
+ :parameters => {},
84
+ :interactive => false,
85
+ :region => default_region,
86
+ :profile => nil,
87
+ :nopretty => false,
88
+ }
86
89
  ARGV.slice_before(/^--/).each do |name, value|
87
90
  case name
88
91
  when '--stack-name'
89
- stack_name = value
92
+ args[:stack_name] = value
90
93
  when '--parameters'
91
- parameters = Hash[value.split(/;/).map { |pair| pair.split(/=/, 2) }] #/# fix for syntax highlighting
94
+ args[:parameters] = Hash[value.split(/;/).map { |pair| pair.split(/=/, 2) }] #/# fix for syntax highlighting
95
+ when '--interactive'
96
+ args[:interactive] = true
92
97
  when '--region'
93
- region = value
98
+ args[:region] = value
94
99
  when '--profile'
95
- profile = value
100
+ args[:profile] = value
96
101
  when '--nopretty'
97
- nopretty = true
102
+ args[:nopretty] = true
98
103
  end
99
104
  end
100
- [stack_name, parameters, region, profile, nopretty]
105
+
106
+ args
101
107
  end
102
108
 
103
109
  def validate_action(action)
@@ -294,13 +300,16 @@ def cfn(template)
294
300
  template_body: template_string,
295
301
  parameters: template.parameters.map { |k,v| {parameter_key: k, parameter_value: v}}.to_a,
296
302
  tags: cfn_tags.map { |k,v| {"key" => k.to_s, "value" => v} }.to_a,
297
- capabilities: ["CAPABILITY_IAM"],
303
+ capabilities: ["CAPABILITY_NAMED_IAM"],
298
304
  }
299
305
 
300
306
  # fill in options from the command line
301
307
  extra_options = parse_arg_array_as_hash(options)
302
308
  create_stack_opts = extra_options.merge(create_stack_opts)
303
309
 
310
+ # remove custom options
311
+ create_stack_opts.delete(:interactive)
312
+
304
313
  # create stack
305
314
  create_result = cfn_client.create_stack(create_stack_opts)
306
315
  if create_result.successful?
@@ -486,13 +495,16 @@ def cfn(template)
486
495
  template_body: template_string,
487
496
  parameters: template.parameters.map { |k,v| {parameter_key: k, parameter_value: v}}.to_a,
488
497
  tags: cfn_tags.map { |k,v| {"key" => k.to_s, "value" => v.to_s} }.to_a,
489
- capabilities: ["CAPABILITY_IAM"],
498
+ capabilities: ["CAPABILITY_NAMED_IAM"],
490
499
  }
491
500
 
492
501
  # fill in options from the command line
493
502
  extra_options = parse_arg_array_as_hash(options)
494
503
  update_stack_opts = extra_options.merge(update_stack_opts)
495
504
 
505
+ # remove custom options
506
+ update_stack_opts.delete(:interactive)
507
+
496
508
  # update the stack
497
509
  update_result = cfn_client.update_stack(update_stack_opts)
498
510
  if update_result.successful?
@@ -567,6 +579,6 @@ end
567
579
 
568
580
  # Main entry point
569
581
  def template(&block)
570
- stack_name, parameters, aws_region, aws_profile, nopretty = parse_args
571
- raw_template(parameters, stack_name, aws_region, aws_profile, nopretty, &block)
582
+ options = parse_args
583
+ raw_template(options, &block)
572
584
  end
@@ -44,8 +44,8 @@ end
44
44
  ############################# CloudFormation DSL
45
45
 
46
46
  # Main entry point
47
- def raw_template(parameters = {}, stack_name = nil, aws_region = default_region, aws_profile = nil, nopretty = false, &block)
48
- TemplateDSL.new(parameters, stack_name, aws_region, aws_profile, nopretty, &block)
47
+ def raw_template(options, &block)
48
+ TemplateDSL.new(options, &block)
49
49
  end
50
50
 
51
51
  def default_region
@@ -54,14 +54,20 @@ end
54
54
 
55
55
  # Core interpreter for the DSL
56
56
  class TemplateDSL < JsonObjectDSL
57
- attr_reader :parameters, :aws_region, :nopretty, :stack_name, :aws_profile
58
-
59
- def initialize(parameters = {}, stack_name = nil, aws_region = default_region, aws_profile = nil, nopretty = false)
60
- @parameters = parameters
61
- @stack_name = stack_name
62
- @aws_region = aws_region
63
- @aws_profile = aws_profile
64
- @nopretty = nopretty
57
+ attr_reader :parameters,
58
+ :parameter_cli,
59
+ :aws_region,
60
+ :nopretty,
61
+ :stack_name,
62
+ :aws_profile
63
+
64
+ def initialize(options)
65
+ @parameters = options[:parameters]
66
+ @interactive = options[:interactive]
67
+ @stack_name = options[:stack_name]
68
+ @aws_region = options[:region]
69
+ @aws_profile = options[:profile]
70
+ @nopretty = options[:nopretty]
65
71
  super()
66
72
  end
67
73
 
@@ -71,7 +77,12 @@ class TemplateDSL < JsonObjectDSL
71
77
 
72
78
  def parameter(name, options)
73
79
  default(:Parameters, {})[name] = options
74
- @parameters[name] ||= options[:Default]
80
+
81
+ if @interactive
82
+ @parameters[name] ||= _get_parameter_from_cli(name, options)
83
+ else
84
+ @parameters[name] ||= options[:Default]
85
+ end
75
86
  end
76
87
 
77
88
  # Find parameters where the specified attribute is true then remove the attribute from the cfn template.
@@ -144,6 +155,8 @@ class TemplateDSL < JsonObjectDSL
144
155
  end
145
156
  end
146
157
 
158
+ def metadata(name, options) default(:Metadata, {})[name] = options end
159
+
147
160
  def condition(name, options) default(:Conditions, {})[name] = options end
148
161
 
149
162
  def resource(name, options) default(:Resources, {})[name] = options end
@@ -160,6 +173,73 @@ class TemplateDSL < JsonObjectDSL
160
173
  { :'Fn::FindInMap' => [ map, key, name ] }
161
174
  end
162
175
  end
176
+
177
+ private
178
+ def _get_parameter_from_cli(name, options)
179
+ # basic request
180
+ param_request = "Parameter '#{name}' (#{options[:Type]})"
181
+
182
+ # add description to request
183
+ if options.has_key?(:Description)
184
+ param_request += "\nDescription: #{options[:Description]}"
185
+ end
186
+
187
+ # add validation to the request
188
+
189
+ # allowed pattern
190
+ if options.has_key?(:AllowedPattern)
191
+ param_request += "\nAllowed Pattern: /#{options[:AllowedPattern]}/"
192
+ end
193
+
194
+ # allowed values
195
+ if options.has_key?(:AllowedValues)
196
+ param_request += "\nAllowed Values: #{options[:AllowedValues].join(', ')}"
197
+ end
198
+
199
+ # min/max length
200
+ if options.has_key?(:MinLength) or options.has_key?(:MaxLength)
201
+ min_length = "-infinity"
202
+ max_length = "+infinity"
203
+ if options.has_key?(:MinLength)
204
+ min_length = options[:MinLength]
205
+ end
206
+ if options.has_key?(:MaxLength)
207
+ max_length = options[:MaxLength]
208
+ end
209
+ param_request += "\nValid Length: #{min_length} < string < #{max_length}"
210
+ end
211
+
212
+ # min/max value
213
+ if options.has_key?(:MinValue) or options.has_key?(:MaxValue)
214
+ min_value = "-infinity"
215
+ max_value = "+infinity"
216
+ if options.has_key?(:MinValue)
217
+ min_value = options[:MinValue]
218
+ end
219
+ if options.has_key?(:MaxValue)
220
+ max_value = options[:MaxValue]
221
+ end
222
+ param_request += "\nValid Number: #{min_value} < number < #{max_value}"
223
+ end
224
+
225
+ # add default to request
226
+ if options.has_key?(:Default) and !options[:Default].nil?
227
+ param_request += "\nLeave value empty for default: #{options[:Default]}"
228
+ end
229
+
230
+ param_request += "\nValue: "
231
+
232
+ # request the param
233
+ $stdout.puts "===================="
234
+ $stdout.print param_request
235
+ input = $stdin.gets.chomp
236
+
237
+ if input.nil? or input.empty?
238
+ options[:Default]
239
+ else
240
+ input
241
+ end
242
+ end
163
243
  end
164
244
 
165
245
  def base64(value) { :'Fn::Base64' => value } end
@@ -170,6 +250,8 @@ def get_att(resource, attribute) { :'Fn::GetAtt' => [ resource, attribute ] } en
170
250
 
171
251
  def get_azs(region = '') { :'Fn::GetAZs' => region } end
172
252
 
253
+ def import_value(value) { :'Fn::ImportValue' => value } end
254
+
173
255
  def join(delim, *list)
174
256
  case list.length
175
257
  when 0 then ''
@@ -15,7 +15,7 @@
15
15
  module Cfn
16
16
  module Ruby
17
17
  module Dsl
18
- VERSION = "1.2.3"
18
+ VERSION = "1.2.4"
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,161 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'open3'
4
+
5
+ ##
6
+ # Error encapsulating information about a failed command
7
+ class CommandError < StandardError
8
+ attr_reader :command, :status, :stderr
9
+
10
+ def initialize(command, status, stderr)
11
+ @command = command
12
+ @status = status
13
+ @stderr = stderr
14
+ super "FAILURE (#{status}) #{stderr}"
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Helpers for dealing with pathing from specs
20
+ module PathHelpers
21
+ ##
22
+ # Returns an absolute path for the specified path that
23
+ # is relative to the project root irregardless of where
24
+ # rspec/ruby is invoked
25
+ def from_project_root(relative_path)
26
+ source_dir = File.expand_path(File.dirname(__FILE__))
27
+ File.join(source_dir, "..", relative_path)
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Mixin containing helper methods for working with
33
+ # commands executed in a subshell
34
+ module CommandHelpers
35
+ include PathHelpers
36
+
37
+ ##
38
+ # Logs the command that failed and raises an exception
39
+ # inlcuding status and stderr
40
+ def cmd_failed(command, status, stderr)
41
+ STDERR.puts("FAILURE executing #{command}")
42
+ raise CommandError.new(command, status, stderr)
43
+ end
44
+
45
+ ##
46
+ # execute a command within a specified directory and
47
+ # return results or yield them to a block if one is
48
+ # given to this method. Results are an array of
49
+ # strings:
50
+ #
51
+ # [stdout, token1, token2, ..., tokenN]
52
+ #
53
+ # where stdout is the captured standard output and
54
+ # the tokens are the words extracted from the output.
55
+ #
56
+ # If the command has a non-zero exit status, an
57
+ # exception is raised including the exit code
58
+ # and stderr.
59
+ #
60
+ # example call:
61
+ # exec_cmd("ls", :within => "/")
62
+ def exec_cmd(cmd, opts={:within => "."})
63
+ exec_dir = from_project_root(opts[:within])
64
+ Dir.chdir(exec_dir) do
65
+ stdout, stderr, status = Open3.capture3(cmd)
66
+ results = stdout.split(" ").unshift(stdout)
67
+
68
+ cmd_failed(cmd, status, stderr) if status != 0
69
+ if (block_given?)
70
+ yield results
71
+ else
72
+ results
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Mixin with helper methods for dealing with JSON generated
80
+ # from cloudformation-ruby-dsl
81
+ module JsonHelpers
82
+ ##
83
+ # Parse the json string, making sure to write all of it to
84
+ # STDERR if parsing fails to make it easier to troubleshoot
85
+ # failures in generated json.
86
+ def jsparse(json_string)
87
+ begin
88
+ JSON.parse(json_string)
89
+ rescue StandardError => e
90
+ STDERR.puts "Error parsing JSON:"
91
+ STDERR.puts json_string
92
+ raise e
93
+ end
94
+ end
95
+ end
96
+
97
+ ##
98
+ # Mixin with helper methods for dealing with files generated
99
+ # from a test/spec
100
+ module FileHelpers
101
+ include PathHelpers
102
+
103
+ ##
104
+ # Delete a file from the spec/tmp directory
105
+ def delete_test_file(filename)
106
+ abs_path = File.join(from_project_root("spec/tmp"), filename)
107
+ FileUtils.rm(abs_path)
108
+ end
109
+
110
+ ##
111
+ # Write a file to the spec/tmp directory
112
+ def write_test_file(filename, contents)
113
+ dest_dir = from_project_root("spec/tmp")
114
+ dest_file = File.join(dest_dir, filename)
115
+
116
+ FileUtils.mkdir_p(dest_dir)
117
+ File.open(dest_file, "w") { |f| f.write(contents) }
118
+ dest_file
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Mixin to assist in using aws cli for validating results of
124
+ # cloudformation-ruby-dsl
125
+ module AwsHelpers
126
+ include CommandHelpers
127
+
128
+ ##
129
+ # Validate a cloudformation template within the spec/tmp directory
130
+ # using aws cli
131
+ def validate_cfn_template(template_name)
132
+ template_path = File.join(from_project_root("spec/tmp"), template_name)
133
+ command = validation_command(template_path)
134
+ exec_cmd(command) do |output|
135
+ begin
136
+ JSON.parse(output.first)
137
+ rescue JSON::ParserError
138
+ STDERR.puts "ERROR parsing output of: #{command}"
139
+ raise
140
+ end
141
+ end
142
+ end
143
+
144
+ def profile
145
+ ENV["AWS_PROFILE"] || "default"
146
+ end
147
+
148
+ def region
149
+ ENV["AWS_REGION"] || "us-east-1"
150
+ end
151
+
152
+ private
153
+
154
+ def validation_command(template_path)
155
+ return <<-EOF
156
+ aws cloudformation validate-template --template-body file://#{template_path} \
157
+ --region #{region} \
158
+ --profile #{profile}
159
+ EOF
160
+ end
161
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.shared_examples "template acceptance validations" do
4
+ include CommandHelpers
5
+ include JsonHelpers
6
+ include FileHelpers
7
+ include AwsHelpers
8
+
9
+ it "should create a valid JSON template from the example ruby template" do
10
+ delete_test_file(json_template)
11
+ json = exec_cmd("./#{ruby_template} expand", :within => "examples").first
12
+ write_test_file(json_template, json)
13
+ validate_cfn_template(json_template)
14
+ end
15
+ end
16
+
17
+ describe "cloudformation-ruby-dsl" do
18
+ context "simplest template" do
19
+ let(:ruby_template) { "simple_template.rb" }
20
+ let(:json_template) { "simple_template.json" }
21
+
22
+ include_examples "template acceptance validations"
23
+ end
24
+
25
+ # TODO validate examples/cloudformation-ruby-script.rb
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudformation-ruby-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shawn Smith
@@ -15,7 +15,7 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2016-08-10 00:00:00.000000000 Z
18
+ date: 2016-10-12 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: detabulator
@@ -115,6 +115,34 @@ dependencies:
115
115
  - - '>='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
+ - !ruby/object:Gem::Dependency
119
+ name: rspec
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ - !ruby/object:Gem::Dependency
133
+ name: pry
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ type: :development
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
118
146
  description: Ruby DSL library that provides a wrapper around the CloudFormation.
119
147
  email:
120
148
  - Shawn.Smith@bazaarvoice.com
@@ -150,6 +178,7 @@ files:
150
178
  - examples/maps/more_maps/map3.json
151
179
  - examples/maps/table.txt
152
180
  - examples/maps/vpc.txt
181
+ - examples/simple_template.rb
153
182
  - examples/userdata.sh
154
183
  - initial_contributions.md
155
184
  - lib/cloudformation-ruby-dsl.rb
@@ -158,6 +187,8 @@ files:
158
187
  - lib/cloudformation-ruby-dsl/spotprice.rb
159
188
  - lib/cloudformation-ruby-dsl/table.rb
160
189
  - lib/cloudformation-ruby-dsl/version.rb
190
+ - spec/spec_helper.rb
191
+ - spec/validation_spec.rb
161
192
  homepage: http://github.com/bazaarvoice/cloudformation-ruby-dsl
162
193
  licenses: []
163
194
  metadata: {}
@@ -183,4 +214,6 @@ signing_key:
183
214
  specification_version: 4
184
215
  summary: Ruby DSL library that provides a wrapper around the CloudFormation. Written
185
216
  by [Bazaarvoice](http://www.bazaarvoice.com).
186
- test_files: []
217
+ test_files:
218
+ - spec/spec_helper.rb
219
+ - spec/validation_spec.rb