stackster 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ ## v0.2.8
2
+
3
+ * Added stack status class
4
+ * Update waits for in progress stacks to complete
5
+ * Added -l switch to set log level
6
+
1
7
  ## v0.2.7
2
8
 
3
9
  * Changed some debug levels
@@ -6,9 +6,9 @@ module Stackster
6
6
 
7
7
  def initialize(args)
8
8
  c = args[:config]
9
- @connect = Fog::AWS::SimpleDB.new :aws_access_key_id => c.access_key,
9
+ @connect = Fog::AWS::SimpleDB.new :aws_access_key_id => c.access_key,
10
10
  :aws_secret_access_key => c.secret_key,
11
- :region => c.region
11
+ :region => c.region
12
12
  end
13
13
 
14
14
  def domains
data/lib/stackster/cli.rb CHANGED
@@ -22,6 +22,8 @@ EOS
22
22
  opt :help, "Display Help"
23
23
  opt :attributes, "= seperated attribute and it's value", :type => :string,
24
24
  :multi => true
25
+ opt :log_level, "Log level: debug, info, warn, error", :type => :string,
26
+ :default => 'info'
25
27
  opt :name, "Stack name to manage", :type => :string
26
28
  opt :template, "Path to the template file", :type => :string
27
29
  end
@@ -31,24 +33,21 @@ EOS
31
33
  case @cmd
32
34
  when 'create', 'destroy', 'delete', 'list', 'update', 'show'
33
35
  @s = Stack.new :name => @opts[:name],
34
- :config => nil
36
+ :logger => StacksterLogger.new(:log_level => @opts[:log_level])
35
37
  end
36
38
 
37
39
  case @cmd
38
40
  when 'create'
39
41
  @s.create :attributes => attributes,
40
42
  :template => @opts[:template]
41
- puts "#{@opts[:name]} created."
42
43
  when 'update'
43
44
  @s.update :attributes => attributes
44
- puts "#{@opts[:name]} updated."
45
45
  when 'destroy', 'delete'
46
46
  @s.destroy
47
- puts "#{@opts[:name]} destroyed."
48
47
  when 'show'
49
48
  puts @s.display.to_yaml
50
49
  when 'list'
51
- puts StackLister.new.all
50
+ puts StackLister.new.all.sort
52
51
  else
53
52
  puts "Unkown command '#{@cmd}'"
54
53
  end
@@ -24,11 +24,17 @@ module Stackster
24
24
  end
25
25
 
26
26
  def save
27
- attributes.merge!('Name' => name)
28
- attributes.each_pair do |k,v|
29
- @logger.debug "Setting attribute #{k}=#{v}"
27
+ set_name_attribute
28
+
29
+ attributes.each_pair do |key,value|
30
+ @logger.debug "Setting attribute #{key}=#{value}"
30
31
  end
31
- sdb_connect.put_attributes('stacks', name, attributes, { :replace => attributes.keys })
32
+
33
+ sdb_connect.put_attributes('stacks',
34
+ name,
35
+ attributes,
36
+ :replace => attributes.keys )
37
+
32
38
  @logger.debug "Save to SimpleDB successful."
33
39
  end
34
40
 
@@ -39,9 +45,13 @@ module Stackster
39
45
 
40
46
  private
41
47
 
48
+ def set_name_attribute
49
+ attributes.merge!('Name' => name)
50
+ end
51
+
42
52
  def get_attributes
43
53
  u = {}
44
- attrs = sdb_connect.select("select * from stacks where itemName() = '#{name}'")
54
+ attrs = sdb_connect.select "select * from stacks where itemName() = '#{name}'"
45
55
  if attrs[name]
46
56
  attrs[name].each_pair { |k, v| u[k] = v.first }
47
57
  end
@@ -1,29 +1,32 @@
1
1
  module Stackster
2
2
  class StacksterLogger
3
3
 
4
- def initialize(args = {})
5
- @logger = args[:logger] ||= Logger.new(STDOUT)
4
+ require 'forwardable'
6
5
 
7
- unless args[:logger]
8
- @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
9
- @logger.formatter = proc do |severity, datetime, progname, msg|
10
- "#{datetime}: #{msg}\n"
11
- end
12
- end
6
+ extend Forwardable
13
7
 
14
- @logger
15
- end
8
+ def_delegators :@logger, :debug, :error, :info, :warn
16
9
 
17
- def debug(msg)
18
- @logger.debug msg
10
+ def initialize(args = {})
11
+ @log_level = args[:log_level] ||= 'info'
12
+ @logger = args[:logger] ||= new_logger(args)
19
13
  end
20
14
 
21
- def info(msg)
22
- @logger.info msg
15
+ private
16
+
17
+ def new_logger(args)
18
+ Logger.new(STDOUT).tap do |l|
19
+ l.datetime_format = '%Y-%m-%dT%H:%M:%S%z'
20
+ l.formatter = proc do |severity, datetime, progname, msg|
21
+ "#{datetime} #{severity} : #{msg}\n"
22
+ end
23
+ l.level = logger_level
24
+ end
23
25
  end
24
26
 
25
- def error(msg)
26
- @logger.error msg
27
+ def logger_level
28
+ Logger.const_get @log_level.upcase
27
29
  end
30
+
28
31
  end
29
32
  end
@@ -17,17 +17,21 @@ module Stackster
17
17
  update
18
18
  else
19
19
  @logger.debug "No Cloud Formation parameters require updating."
20
- return false
20
+ false
21
21
  end
22
22
  end
23
23
 
24
24
  private
25
25
 
26
26
  def update
27
- @logger.info "Updating Cloud Formation stack #{@name}."
28
- cloud_formation.update :name => @name,
29
- :parameters => read_parameters_from_entry_attributes,
30
- :template => @template_body
27
+ if status.wait_for_stable
28
+ @logger.info "Updating Cloud Formation stack #{@name}."
29
+ cloud_formation.update :name => @name,
30
+ :parameters => read_parameters_from_entry_attributes,
31
+ :template => @template_body
32
+ else
33
+ raise "#{@name} did not reach a stable state."
34
+ end
31
35
  end
32
36
 
33
37
  def parameter_updated?(attributes)
@@ -56,5 +60,10 @@ module Stackster
56
60
  @cloud_formation ||= AWS::CloudFormation.new :config => @config
57
61
  end
58
62
 
63
+ def status
64
+ @status ||= Status.new :name => @name,
65
+ :config => @config
66
+ end
67
+
59
68
  end
60
69
  end
@@ -0,0 +1,55 @@
1
+ module Stackster
2
+ class Status
3
+
4
+ def initialize(args)
5
+ @name = args[:name]
6
+ @config = args[:config]
7
+ @logger = @config.logger
8
+ end
9
+
10
+ def complete?
11
+ /_COMPLETE$/ === current
12
+ end
13
+
14
+ def failed?
15
+ /_FAILED$/ === current
16
+ end
17
+
18
+ def cleanup_in_progress?
19
+ /_CLEANUP_IN_PROGRESS$/ === current
20
+ end
21
+
22
+ def in_progress?
23
+ /_IN_PROGRESS$/ === current && !cleanup_in_progress?
24
+ end
25
+
26
+ def create_failed?
27
+ 'CREATE_FAILED' == current
28
+ end
29
+
30
+ def stable?
31
+ (complete? || failed?) && (! create_failed?)
32
+ end
33
+
34
+ def wait_for_stable(count=25)
35
+ 1.upto(count).each do |c|
36
+ break if stable?
37
+ @logger.info ("#{@name} not stable (#{current}). Sleeping #{c * c} second(s).")
38
+ Kernel.sleep (c * c)
39
+ end
40
+ stable?
41
+ end
42
+
43
+ private
44
+
45
+ def current
46
+ stack_reader.status
47
+ end
48
+
49
+ def stack_reader
50
+ @stack_reader ||= StackReader.new :name => @name,
51
+ :config => @config
52
+ end
53
+
54
+ end
55
+ end
@@ -4,6 +4,7 @@ require 'stackster/stack/stack_destroyer'
4
4
  require 'stackster/stack/stack_reader'
5
5
  require 'stackster/stack/stack_formater'
6
6
  require 'stackster/stack/stack_lister'
7
+ require 'stackster/stack/status'
7
8
 
8
9
  module Stackster
9
10
  class Stack
@@ -112,5 +113,10 @@ module Stackster
112
113
  :config => @config
113
114
  end
114
115
 
116
+ def status
117
+ @status ||= Status.new :name => @name,
118
+ :config => @config
119
+ end
120
+
115
121
  end
116
122
  end
@@ -1,3 +1,3 @@
1
1
  module Stackster
2
- VERSION = "0.2.7"
2
+ VERSION = "0.2.8"
3
3
  end
data/spec/logger_spec.rb CHANGED
@@ -13,6 +13,9 @@ describe Stackster do
13
13
  logger_mock = mock 'logger'
14
14
  Logger.should_receive(:new).with(STDOUT).and_return logger_mock
15
15
  logger_mock.should_receive(:info).with 'a message'
16
+ logger_mock.should_receive(:datetime_format=).with '%Y-%m-%dT%H:%M:%S%z'
17
+ logger_mock.should_receive(:formatter=)
18
+ logger_mock.should_receive(:level=).with 1
16
19
  logger = Stackster::StacksterLogger.new
17
20
  logger.info 'a message'
18
21
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+
2
2
  require 'json'
3
3
 
4
4
  describe Stackster do
@@ -18,11 +18,12 @@ describe Stackster do
18
18
  }'
19
19
  end
20
20
 
21
- it "should update the stack when parameters change" do
21
+ it "should update the stack when parameters change and stack is stable" do
22
22
  attributes = { "param1" => "value1", "param3" => "value3" }
23
23
  config_mock = mock 'config mock'
24
24
  logger_mock = mock 'logger mock'
25
25
  entry_mock = mock 'entry mock'
26
+ status_mock = mock 'status mock'
26
27
  cloud_formation_mock = mock 'cloud formation mock'
27
28
  Stackster::AWS::CloudFormation.should_receive(:new).
28
29
  with(:config => config_mock).
@@ -36,6 +37,11 @@ describe Stackster do
36
37
  config_mock.should_receive(:logger).and_return(logger_mock)
37
38
  logger_mock.should_receive(:debug).exactly(1).times
38
39
  logger_mock.should_receive(:info).exactly(1).times
40
+ Stackster::Status.should_receive(:new).
41
+ with(:name => 'test-stack',
42
+ :config => config_mock).
43
+ and_return status_mock
44
+ status_mock.should_receive(:wait_for_stable).and_return true
39
45
  stack_updater = Stackster::StackUpdater.new :name => 'test-stack',
40
46
  :template_body => @template_body,
41
47
  :entry => entry_mock,
@@ -45,6 +51,31 @@ describe Stackster do
45
51
  should == true
46
52
  end
47
53
 
54
+ it "should raise an error when parameters change and stack is not stable" do
55
+ attributes = { "param1" => "value1", "param3" => "value3" }
56
+ config_mock = mock 'config mock'
57
+ logger_mock = mock 'logger mock'
58
+ entry_mock = mock 'entry mock'
59
+ status_mock = mock 'status mock'
60
+ cloud_formation_mock = mock 'cloud formation mock'
61
+ Stackster::AWS::CloudFormation.should_receive(:new).
62
+ exactly(0).times
63
+ config_mock.should_receive(:logger).and_return(logger_mock)
64
+ logger_mock.should_receive(:debug).exactly(1).times
65
+ Stackster::Status.should_receive(:new).
66
+ with(:name => 'test-stack',
67
+ :config => config_mock).
68
+ and_return status_mock
69
+ status_mock.should_receive(:wait_for_stable).and_return false
70
+ stack_updater = Stackster::StackUpdater.new :name => 'test-stack',
71
+ :template_body => @template_body,
72
+ :entry => entry_mock,
73
+ :config => config_mock
74
+
75
+ lambda {stack_updater.update_stack_if_parameters_changed( [ { 'param1' => 'new-value' } ] ) }.
76
+ should raise_error
77
+ end
78
+
48
79
  it "should not update the stack when parameters don't change" do
49
80
  attributes = { "param3" => "value3" }
50
81
  config_mock = mock 'config mock'
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stackster do
4
+
5
+ before do
6
+ @config_mock = mock 'config mock'
7
+ @logger_mock = mock 'logger mock'
8
+ @stack_reader_mock = mock 'stack reader mock'
9
+ Stackster::StackReader.should_receive(:new).
10
+ and_return @stack_reader_mock
11
+ @config_mock.should_receive(:logger).and_return @logger_mock
12
+ @status = Stackster::Status.new :name => 'test-stack',
13
+ :config => @config_mock
14
+ end
15
+
16
+ it "should return true if the stack is in complete state" do
17
+ @stack_reader_mock.should_receive(:status).
18
+ and_return 'UPDATE_COMPLETE'
19
+ @status.complete?.should == true
20
+ end
21
+
22
+ it "should return false if the stack is not in complete state" do
23
+ @stack_reader_mock.should_receive(:status).
24
+ and_return 'UPDATE_IN_PROGRESS'
25
+ @status.complete?.should == false
26
+ end
27
+
28
+ it "should return true if the stack is in a failed state" do
29
+ @stack_reader_mock.should_receive(:status).
30
+ and_return 'DELETE_FAILED'
31
+ @status.failed?.should == true
32
+ end
33
+
34
+ it "should return false if the stack is not in a failed state" do
35
+ @stack_reader_mock.should_receive(:status).
36
+ and_return 'UPDATE_IN_PROGRESS'
37
+ @status.failed?.should == false
38
+ end
39
+
40
+ it "should return true if the stack is in an in_progress state" do
41
+ @stack_reader_mock.should_receive(:status).exactly(2).times.
42
+ and_return 'UPDATE_IN_PROGRESS'
43
+ @status.in_progress?.should == true
44
+ end
45
+
46
+ it "should return false if the stack is not in an in_progress state" do
47
+ @stack_reader_mock.should_receive(:status).exactly(2).times.
48
+ and_return 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'
49
+ @status.in_progress?.should == false
50
+ end
51
+
52
+ it "should return true if the stack is cleaning up" do
53
+ @stack_reader_mock.should_receive(:status).
54
+ and_return 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'
55
+ @status.cleanup_in_progress?.should == true
56
+ end
57
+
58
+ it "should return false if the stack is not in a cleaning up state" do
59
+ @stack_reader_mock.should_receive(:status).
60
+ and_return 'UPDATE_IN_PROGRESS'
61
+ @status.cleanup_in_progress?.should == false
62
+ end
63
+
64
+ it "should return true if the stack is in a complete state" do
65
+ @stack_reader_mock.should_receive(:status).exactly(2).times.
66
+ and_return 'UPDATE_COMPLETE'
67
+ @status.stable?.should == true
68
+ end
69
+
70
+ it "should return true if the stack is in a failed state" do
71
+ @stack_reader_mock.should_receive(:status).exactly(3).times.
72
+ and_return 'UPDATE_FAILED'
73
+ @status.stable?.should == true
74
+ end
75
+
76
+ it "should return true if the stack is in an in progress state" do
77
+ @stack_reader_mock.should_receive(:status).exactly(2).times.
78
+ and_return 'IN_PROGRESS'
79
+ @status.stable?.should == false
80
+ end
81
+
82
+ it "should return false if the stack creation failed" do
83
+ @stack_reader_mock.should_receive(:status).exactly(3).times.
84
+ and_return 'CREATE_FAILED'
85
+ @status.stable?.should == false
86
+ end
87
+
88
+ it "should return true when the stack is in a stable state" do
89
+ @stack_reader_mock.should_receive(:status).exactly(4).times.
90
+ and_return 'CREATE_COMPLETE'
91
+ @status.wait_for_stable.should == true
92
+ end
93
+
94
+ it "should sleep 2 times and return false when the stack is in an unstable state" do
95
+ Kernel.stub!(:sleep)
96
+ Kernel.should_receive(:sleep).with(1)
97
+ Kernel.should_receive(:sleep).with(4)
98
+
99
+ @stack_reader_mock.should_receive(:status).exactly(11).times.
100
+ and_return 'CREATE_FAILED'
101
+ @logger_mock.should_receive(:info).exactly(2).times
102
+ @status.wait_for_stable(2).should == false
103
+ end
104
+
105
+ end
data/spec/stack_spec.rb CHANGED
@@ -41,13 +41,15 @@ describe Stackster do
41
41
 
42
42
  it "should create a new stack" do
43
43
  stack_creater_mock = mock 'stack creater'
44
- @entry_mock.should_receive(:set_attributes).with({ 'test-attr' => 'test-value' })
44
+ @entry_mock.should_receive(:set_attributes).
45
+ with({ 'test-attr' => 'test-value' })
45
46
  @entry_mock.should_receive(:save)
46
- Stackster::StackCreater.should_receive(:new).with(:name => 'test-stack',
47
- :entry => @entry_mock,
48
- :template_file => 'template_file',
49
- :config => @config_mock).
50
- and_return stack_creater_mock
47
+ Stackster::StackCreater.should_receive(:new).
48
+ with(:name => 'test-stack',
49
+ :entry => @entry_mock,
50
+ :template_file => 'template_file',
51
+ :config => @config_mock).
52
+ and_return stack_creater_mock
51
53
  stack_creater_mock.should_receive(:create)
52
54
 
53
55
  @stack.create :template => 'template_file',
@@ -68,15 +70,18 @@ describe Stackster do
68
70
  @entry_mock.should_receive(:save)
69
71
  stack_reader_mock.should_receive(:template).and_return('template-body-json')
70
72
 
71
- Stackster::StackReader.should_receive(:new).with(:name => 'test-stack',
72
- :config => @config_mock).
73
- and_return stack_reader_mock
74
- Stackster::StackUpdater.should_receive(:new).with(:name => 'test-stack',
75
- :entry => @entry_mock,
76
- :template_body => 'template-body-json',
77
- :config => @config_mock).
78
- and_return stack_updater_mock
79
- stack_updater_mock.should_receive(:update_stack_if_parameters_changed)
73
+ Stackster::StackReader.should_receive(:new).
74
+ with(:name => 'test-stack',
75
+ :config => @config_mock).
76
+ and_return stack_reader_mock
77
+ Stackster::StackUpdater.should_receive(:new).
78
+ with(:name => 'test-stack',
79
+ :entry => @entry_mock,
80
+ :template_body => 'template-body-json',
81
+ :config => @config_mock).
82
+ and_return stack_updater_mock
83
+ stack_updater_mock.should_receive(:update_stack_if_parameters_changed).
84
+ and_return true
80
85
  @stack.update :attributes => { 'update' => 'test-attrs' }
81
86
  end
82
87
 
@@ -86,9 +91,10 @@ describe Stackster do
86
91
  context "when destroying a stack" do
87
92
  it "should destroy a stack" do
88
93
  stack_destroyer_mock = mock 'stack destroyer'
89
- Stackster::StackDestroyer.should_receive(:new).with(:name => 'test-stack',
90
- :config => @config_mock).
91
- and_return stack_destroyer_mock
94
+ Stackster::StackDestroyer.should_receive(:new).
95
+ with(:name => 'test-stack',
96
+ :config => @config_mock).
97
+ and_return stack_destroyer_mock
92
98
  stack_destroyer_mock.should_receive(:destroy)
93
99
  @entry_mock.should_receive(:delete_attributes)
94
100
  @stack.destroy
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-19 00:00:00.000000000 Z
12
+ date: 2012-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70336360579600 !ruby/object:Gem::Requirement
16
+ requirement: &70204471061060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70336360579600
24
+ version_requirements: *70204471061060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: fog
27
- requirement: &70336360578840 !ruby/object:Gem::Requirement
27
+ requirement: &70204471059380 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70336360578840
35
+ version_requirements: *70204471059380
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: logger
38
- requirement: &70336360578160 !ruby/object:Gem::Requirement
38
+ requirement: &70204471055480 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70336360578160
46
+ version_requirements: *70204471055480
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: trollop
49
- requirement: &70336360576700 !ruby/object:Gem::Requirement
49
+ requirement: &70204471053160 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70336360576700
57
+ version_requirements: *70204471053160
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: xml-simple
60
- requirement: &70336360574960 !ruby/object:Gem::Requirement
60
+ requirement: &70204471051140 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70336360574960
68
+ version_requirements: *70204471051140
69
69
  description: Thats what I do
70
70
  email:
71
71
  - brett@weav.net
@@ -101,6 +101,7 @@ files:
101
101
  - lib/stackster/stack/stack_lister.rb
102
102
  - lib/stackster/stack/stack_reader.rb
103
103
  - lib/stackster/stack/stack_updater.rb
104
+ - lib/stackster/stack/status.rb
104
105
  - lib/stackster/version.rb
105
106
  - script/ci_setup
106
107
  - spec/aws/cloud_formation/error_spec.rb
@@ -119,6 +120,7 @@ files:
119
120
  - spec/stack/stack_lister_spec.rb
120
121
  - spec/stack/stack_reader_spec.rb
121
122
  - spec/stack/stack_updater_spec.rb
123
+ - spec/stack/status_spec.rb
122
124
  - spec/stack_spec.rb
123
125
  - stackster.gemspec
124
126
  homepage: ''
@@ -135,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
137
  version: '0'
136
138
  segments:
137
139
  - 0
138
- hash: -121993254634425492
140
+ hash: -1998118593165875979
139
141
  required_rubygems_version: !ruby/object:Gem::Requirement
140
142
  none: false
141
143
  requirements:
@@ -144,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
146
  version: '0'
145
147
  segments:
146
148
  - 0
147
- hash: -121993254634425492
149
+ hash: -1998118593165875979
148
150
  requirements: []
149
151
  rubyforge_project: stackster
150
152
  rubygems_version: 1.8.16
@@ -168,4 +170,5 @@ test_files:
168
170
  - spec/stack/stack_lister_spec.rb
169
171
  - spec/stack/stack_reader_spec.rb
170
172
  - spec/stack/stack_updater_spec.rb
173
+ - spec/stack/status_spec.rb
171
174
  - spec/stack_spec.rb