marathon-api 0.9.0 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +2 -2
  3. data/.travis.yml +1 -1
  4. data/README.md +40 -14
  5. data/bin/marathon +242 -0
  6. data/fixtures/marathon_docker_sample_2.json +2 -1
  7. data/fixtures/vcr/Marathon_App/_restart/restarts_an_app.yml +32 -0
  8. data/fixtures/vcr/Marathon_Group/_changes/changes_the_group.yml +61 -0
  9. data/fixtures/vcr/Marathon_Group/_delete/deletes_the_group.yml +32 -0
  10. data/fixtures/vcr/Marathon_Group/_delete/fails_deleting_not_existing_app.yml +32 -0
  11. data/fixtures/vcr/Marathon_Group/_get/fails_getting_not_existing_app.yml +32 -0
  12. data/fixtures/vcr/Marathon_Group/_get/gets_the_group.yml +32 -0
  13. data/fixtures/vcr/Marathon_Group/_list/lists_apps.yml +33 -0
  14. data/fixtures/vcr/Marathon_Group/_start/fails_getting_not_existing_group.yml +32 -0
  15. data/fixtures/vcr/Marathon_Group/_start/starts_the_group.yml +35 -0
  16. data/lib/marathon.rb +33 -5
  17. data/lib/marathon/app.rb +72 -22
  18. data/lib/marathon/base.rb +32 -0
  19. data/lib/marathon/connection.rb +32 -22
  20. data/lib/marathon/constraint.rb +39 -0
  21. data/lib/marathon/container.rb +41 -0
  22. data/lib/marathon/container_docker.rb +33 -0
  23. data/lib/marathon/container_docker_port_mapping.rb +32 -0
  24. data/lib/marathon/container_volume.rb +31 -0
  25. data/lib/marathon/deployment.rb +5 -15
  26. data/lib/marathon/deployment_info.rb +20 -0
  27. data/lib/marathon/error.rb +8 -2
  28. data/lib/marathon/group.rb +166 -0
  29. data/lib/marathon/health_check.rb +35 -0
  30. data/lib/marathon/queue.rb +4 -13
  31. data/lib/marathon/task.rb +15 -12
  32. data/lib/marathon/util.rb +47 -3
  33. data/lib/marathon/version.rb +1 -1
  34. data/marathon-api.gemspec +4 -3
  35. data/spec/marathon/app_spec.rb +108 -50
  36. data/spec/marathon/base_spec.rb +53 -0
  37. data/spec/marathon/connection_spec.rb +1 -1
  38. data/spec/marathon/constraint_spec.rb +27 -0
  39. data/spec/marathon/container_docker_port_mapping_spec.rb +55 -0
  40. data/spec/marathon/container_docker_spec.rb +42 -0
  41. data/spec/marathon/container_spec.rb +40 -0
  42. data/spec/marathon/container_volume_spec.rb +50 -0
  43. data/spec/marathon/deployment_info_spec.rb +43 -0
  44. data/spec/marathon/deployment_spec.rb +15 -16
  45. data/spec/marathon/error_spec.rb +17 -0
  46. data/spec/marathon/group_spec.rb +172 -0
  47. data/spec/marathon/health_check_spec.rb +50 -0
  48. data/spec/marathon/marathon_spec.rb +31 -0
  49. data/spec/marathon/queue_spec.rb +2 -2
  50. data/spec/marathon/task_spec.rb +24 -11
  51. data/spec/marathon/util_spec.rb +21 -1
  52. metadata +58 -6
@@ -0,0 +1,35 @@
1
+ # This class represents a Marathon HealthCheck.
2
+ # See https://mesosphere.github.io/marathon/docs/health-checks.html for full details.
3
+ class Marathon::HealthCheck < Marathon::Base
4
+
5
+ DEFAULTS = {
6
+ :gracePeriodSeconds => 300,
7
+ :intervalSeconds => 60,
8
+ :maxConsecutiveFailures => 3,
9
+ :path => '/',
10
+ :portIndex => 0,
11
+ :protocol => 'HTTP',
12
+ :timeoutSeconds => 20
13
+ }
14
+
15
+ ACCESSORS = %w[ command gracePeriodSeconds intervalSeconds maxConsecutiveFailures
16
+ path portIndex protocol timeoutSeconds ]
17
+
18
+ # Create a new health check object.
19
+ # ++hash++: Hash returned by API.
20
+ def initialize(hash)
21
+ super(Marathon::Util.merge_keywordized_hash(DEFAULTS, hash), ACCESSORS)
22
+ Marathon::Util.validate_choice(:protocol, protocol, %w[HTTP TCP COMMAND])
23
+ end
24
+
25
+ def to_s
26
+ if protocol == 'COMMAND'
27
+ "Marathon::HealthCheck { :protocol => #{protocol} :command => #{command} }"
28
+ elsif protocol == 'HTTP'
29
+ "Marathon::HealthCheck { :protocol => #{protocol} :portIndex => #{portIndex} :path => #{path} }"
30
+ else
31
+ "Marathon::HealthCheck { :protocol => #{protocol} :portIndex => #{portIndex} }"
32
+ end
33
+ end
34
+
35
+ end
@@ -1,29 +1,20 @@
1
1
  # This class represents a Marathon Queue element.
2
2
  # See https://mesosphere.github.io/marathon/docs/rest-api.html#queue for full list of API's methods.
3
- class Marathon::Queue
3
+ class Marathon::Queue < Marathon::Base
4
4
 
5
5
  attr_reader :app
6
- attr_reader :delay
7
6
 
8
7
  # Create a new queue element object.
9
8
  # ++hash++: Hash returned by API, including 'app' and 'delay'
10
- def initialize(hash = {})
11
- @app = Marathon::App.new(hash['app'], true)
12
- @delay = hash['delay']
9
+ def initialize(hash)
10
+ super(hash, %w[delay])
11
+ @app = Marathon::App.new(info[:app], true)
13
12
  end
14
13
 
15
14
  def to_s
16
15
  "Marathon::Queue { :appId => #{app.id} :delay => #{delay} }"
17
16
  end
18
17
 
19
- # Return queue element as JSON formatted string.
20
- def to_json
21
- {
22
- 'app' => @app.info,
23
- 'delay' => @delay
24
- }.to_json
25
- end
26
-
27
18
  class << self
28
19
 
29
20
  # Show content of the task queue.
@@ -1,18 +1,13 @@
1
1
  # This class represents a Marathon Task.
2
2
  # See https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/tasks for full list of API's methods.
3
- class Marathon::Task
3
+ class Marathon::Task < Marathon::Base
4
4
 
5
- attr_reader :info
5
+ ACCESSORS = %w[ id appId host ports servicePorts version stagedAt startedAt ]
6
6
 
7
7
  # Create a new task object.
8
8
  # ++hash++: Hash including all attributes
9
- def initialize(hash = {})
10
- @info = hash
11
- end
12
-
13
- # Shortcuts for reaching attributes
14
- %w[ id appId host ports servicePorts version stagedAt startedAt ].each do |method|
15
- define_method(method) { |*args, &block| info[method] }
9
+ def initialize(hash)
10
+ super(hash, ACCESSORS)
16
11
  end
17
12
 
18
13
  # Kill the task that belongs to an application.
@@ -28,9 +23,17 @@ class Marathon::Task
28
23
  "Marathon::Task { :id => #{self.id} :appId => #{appId} :host => #{host} }"
29
24
  end
30
25
 
31
- # Return task as JSON formatted string.
32
- def to_json
33
- info.to_json
26
+ # Returns a string for listing the task.
27
+ def to_pretty_s
28
+ %Q[
29
+ Task ID: #{id}
30
+ App ID: #{appId}
31
+ Host: #{host}
32
+ Ports: #{(ports || []).join(',')}
33
+ Staged at: #{stagedAt}
34
+ Started at: #{startedAt}
35
+ Version: #{version}
36
+ ].strip
34
37
  end
35
38
 
36
39
  class << self
@@ -8,6 +8,7 @@ class Marathon::Util
8
8
  # ++allowed++: array of allowd values
9
9
  # ++nil_allowed++: allow nil values
10
10
  def validate_choice(name, value, allowed, nil_allowed = true)
11
+ value = value[name] if value.is_a?(Hash)
11
12
  if value.nil?
12
13
  unless nil_allowed
13
14
  raise Marathon::Error::ArgumentError, "#{name} must not be nil"
@@ -15,8 +16,13 @@ class Marathon::Util
15
16
  else
16
17
  # value is not nil
17
18
  unless allowed.include?(value)
18
- msg = nil_allowed ? "#{name} must be one of #{allowed}, or nil" : "#{name} must be one of #{allowed}"
19
- raise Marathon::Error::ArgumentError, msg
19
+ if nil_allowed
20
+ raise Marathon::Error::ArgumentError,
21
+ "#{name} must be one of #{allowed.join(', ')} or nil, but is '#{value}'"
22
+ else
23
+ raise Marathon::Error::ArgumentError,
24
+ "#{name} must be one of #{allowed.join(', ')} or nil, but is '#{value}'"
25
+ end
20
26
  end
21
27
  end
22
28
  end
@@ -31,5 +37,43 @@ class Marathon::Util
31
37
  validate_choice(name, value, allowed, nil_allowed)
32
38
  opts[name] = value if value
33
39
  end
40
+
41
+ # Swap keys of the hash against their symbols.
42
+ # ++hash++: the hash
43
+ def keywordize_hash(hash)
44
+ if hash.is_a?(Hash)
45
+ new_hash = {}
46
+ hash.each do |k,v|
47
+ key = k.to_sym
48
+ if key == :env and v.is_a?(Hash)
49
+ # ignore :env => {} to prevent renameing environment variables
50
+ new_hash[key] = hash[k]
51
+ else
52
+ new_hash[key] = keywordize_hash(hash[k])
53
+ end
54
+ end
55
+ new_hash
56
+ elsif hash.is_a?(Array)
57
+ hash.map { |e| keywordize_hash(e) }
58
+ else
59
+ hash
60
+ end
61
+ end
62
+
63
+ # Merge two hashes but keywordize both.
64
+ def merge_keywordized_hash(h1, h2)
65
+ keywordize_hash(h1).merge(keywordize_hash(h2))
66
+ end
67
+
68
+ # Stringify an item or an array of items.
69
+ def items_to_pretty_s(item)
70
+ if item.nil?
71
+ nil
72
+ elsif item.is_a?(Array)
73
+ item.map {|e| e.to_pretty_s}.join(',')
74
+ else
75
+ item.to_pretty_s
76
+ end
77
+ end
34
78
  end
35
- end
79
+ end
@@ -1,3 +1,3 @@
1
1
  module Marathon
2
- VERSION = '0.9.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -5,10 +5,10 @@ Gem::Specification.new do |gem|
5
5
  gem.name = "marathon-api"
6
6
  gem.version = Marathon::VERSION
7
7
  gem.authors = ["Felix Bechstein"]
8
- gem.email = %w{f@ub0r.de}
8
+ gem.email = %w{felix.bechstein@otto.de}
9
9
  gem.description = %q{A simple REST client for the Marathon Remote API}
10
10
  gem.summary = %q{A simple REST client for the Marathon Remote API}
11
- gem.homepage = 'https://github.com/felixb/marathon-api'
11
+ gem.homepage = 'https://github.com/otto-de/marathon-api'
12
12
  gem.license = 'MIT'
13
13
 
14
14
  gem.files = `git ls-files`.split($\)
@@ -17,7 +17,8 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = %w{lib}
18
18
 
19
19
  gem.add_dependency 'json'
20
- gem.add_dependency "httparty", ">= 0.11"
20
+ gem.add_dependency 'httparty', '>= 0.11'
21
+ gem.add_dependency 'trollop', '>= 2.0'
21
22
 
22
23
  gem.add_development_dependency 'rake'
23
24
  gem.add_development_dependency 'rspec', '~> 3.0'
@@ -3,20 +3,42 @@ require 'spec_helper'
3
3
  describe Marathon::App do
4
4
 
5
5
  describe '#to_s' do
6
- subject { described_class.new({ 'id' => '/app/foo' }) }
6
+ subject { described_class.new({
7
+ 'id' => '/app/foo',
8
+ 'instances' => 1,
9
+ 'tasks' => [],
10
+ 'env' => {'FOO' => 'BAR', 'blubb' => 'blah'},
11
+ 'constraints' => [['hostname', 'UNIQUE']],
12
+ 'uris' => ['http://example.com/big.tar'],
13
+ 'version' => 'foo-version'
14
+ }) }
7
15
 
8
16
  let(:expected_string) do
9
17
  "Marathon::App { :id => /app/foo }"
10
18
  end
11
19
 
20
+ let(:expected_pretty_string) do
21
+ "App ID: /app/foo\n" + \
22
+ "Instances: 0/1\n" + \
23
+ "Command: \n" + \
24
+ "CPUs: \n" + \
25
+ "Memory: MB\n" + \
26
+ "URI: http://example.com/big.tar\n" + \
27
+ "ENV: FOO=BAR\n" + \
28
+ "ENV: blubb=blah\n" + \
29
+ "Constraint: hostname:UNIQUE\n" + \
30
+ "Version: foo-version"
31
+ end
32
+
12
33
  its(:to_s) { should == expected_string }
34
+ its(:to_pretty_s) { should == expected_pretty_string }
13
35
  end
14
36
 
15
37
  describe '#to_json' do
16
38
  subject { described_class.new({ 'id' => '/app/foo' }) }
17
39
 
18
40
  let(:expected_string) do
19
- '{"id":"/app/foo"}'
41
+ '{"constraints":[],"env":{},"ports":[],"uris":[],"id":"/app/foo"}'
20
42
  end
21
43
 
22
44
  its(:to_json) { should == expected_string }
@@ -30,12 +52,41 @@ describe Marathon::App do
30
52
  end
31
53
  end
32
54
 
55
+ describe '#container' do
56
+ subject { described_class.new({
57
+ 'id' => '/ubuntu2', 'container' => {'type'=>'DOCKER', 'docker'=>{'image'=>'felixb/yocto-httpd'}}
58
+ })}
59
+
60
+ it 'has container' do
61
+ expect(subject.container).to be_instance_of(Marathon::Container)
62
+ expect(subject.container.type).to eq('DOCKER')
63
+ end
64
+ end
65
+
66
+ describe '#constraints' do
67
+ subject { described_class.new({ 'id' => '/ubuntu2', 'constraints' => [['hostname', 'UNIQUE']] }) }
68
+
69
+ it 'has constraints' do
70
+ expect(subject.constraints).to be_instance_of(Array)
71
+ expect(subject.constraints.first).to be_instance_of(Marathon::Constraint)
72
+ end
73
+ end
74
+
75
+ describe '#constraints' do
76
+ subject { described_class.new({ 'id' => '/ubuntu2', 'healthChecks' => [{ 'path' => '/ping' }] }) }
77
+
78
+ it 'has healthChecks' do
79
+ expect(subject.healthChecks).to be_instance_of(Array)
80
+ expect(subject.healthChecks.first).to be_instance_of(Marathon::HealthCheck)
81
+ end
82
+ end
83
+
33
84
  describe '#tasks' do
34
85
  subject { described_class.new({ 'id' => '/ubuntu2' }) }
35
86
 
36
87
  it 'checks for read only' do
37
88
  expect(subject).to receive(:check_read_only)
38
- subject.info['tasks'] = []
89
+ subject.info[:tasks] = []
39
90
  subject.tasks
40
91
  end
41
92
 
@@ -48,13 +99,13 @@ describe Marathon::App do
48
99
  end
49
100
 
50
101
  it 'loads tasks from API when not loaded already' do
51
- subject.info['tasks'] = nil
52
- expect(subject).to receive(:refresh) { subject.info['tasks'] = [] }
102
+ subject.info[:tasks] = nil
103
+ expect(subject).to receive(:refresh) { subject.info[:tasks] = [] }
53
104
  expect(subject.tasks).to eq([])
54
105
  end
55
106
 
56
107
  it 'shows already loaded tasks w/o API call' do
57
- subject.info['tasks'] = []
108
+ subject.info[:tasks] = []
58
109
  expect(subject).not_to receive(:refresh)
59
110
  expect(subject.tasks).to eq([])
60
111
  end
@@ -77,29 +128,30 @@ describe Marathon::App do
77
128
  end
78
129
 
79
130
  describe '#start!' do
80
- let(:app) { described_class.new({ 'id' => '/app/foo' }) }
131
+ subject { described_class.new({ 'id' => '/app/foo' }) }
81
132
 
82
133
  it 'checks for read only' do
83
134
  expect(subject).to receive(:check_read_only)
84
- expect(described_class).to receive(:start) { described_class.new }
135
+ expect(described_class).to receive(:start) { described_class.new('id' => subject.id) }
85
136
  subject.start!
86
137
  end
87
138
 
88
139
  it 'starts the app' do
89
- expect(described_class).to receive(:start).with({ 'id' => '/app/foo'}) do
90
- described_class.new({ 'id' => '/app/foo', 'started' => true })
140
+ expect(described_class).to receive(:start)
141
+ .with({:constraints=>[], :env=>{}, :ports=>[], :uris=>[], :id=>"/app/foo"}) do
142
+ described_class.new({ 'id' => '/app/foo', 'started' => true })
91
143
  end
92
- app.start!
93
- expect(app.info['started']).to be(true)
144
+ subject.start!
145
+ expect(subject.info[:started]).to be(true)
94
146
  end
95
147
  end
96
148
 
97
149
  describe '#refresh' do
98
- let(:app) { described_class.new({ 'id' => '/app/foo' }) }
150
+ subject { described_class.new({ 'id' => '/app/foo' }) }
99
151
 
100
152
  it 'checks for read only' do
101
153
  expect(subject).to receive(:check_read_only)
102
- expect(described_class).to receive(:get) { described_class.new }
154
+ expect(described_class).to receive(:get) { described_class.new('id' => subject.id) }
103
155
  subject.refresh
104
156
  end
105
157
 
@@ -107,13 +159,13 @@ describe Marathon::App do
107
159
  expect(described_class).to receive(:get).with('/app/foo') do
108
160
  described_class.new({ 'id' => '/app/foo', 'refreshed' => true })
109
161
  end
110
- app.refresh
111
- expect(app.info['refreshed']).to be(true)
162
+ subject.refresh
163
+ expect(subject.info[:refreshed]).to be(true)
112
164
  end
113
165
  end
114
166
 
115
167
  describe '#restart!' do
116
- let(:app) { described_class.new({ 'id' => '/app/foo' }) }
168
+ subject { described_class.new({ 'id' => '/app/foo' }) }
117
169
 
118
170
  it 'checks for read only' do
119
171
  expect(subject).to receive(:check_read_only)
@@ -124,18 +176,18 @@ describe Marathon::App do
124
176
  it 'restarts the app' do
125
177
  expect(described_class).to receive(:restart)
126
178
  .with('/app/foo', false)
127
- app.restart!
179
+ subject.restart!
128
180
  end
129
181
 
130
182
  it 'restarts the app, force' do
131
183
  expect(described_class).to receive(:restart)
132
184
  .with('/app/foo', true)
133
- app.restart!(true)
185
+ subject.restart!(true)
134
186
  end
135
187
  end
136
188
 
137
189
  describe '#change!' do
138
- let(:app) { described_class.new({ 'id' => '/app/foo' }) }
190
+ subject { described_class.new({ 'id' => '/app/foo' }) }
139
191
 
140
192
  it 'checks for read only' do
141
193
  expect(subject).to receive(:check_read_only)
@@ -145,12 +197,12 @@ describe Marathon::App do
145
197
 
146
198
  it 'changes the app' do
147
199
  expect(described_class).to receive(:change).with('/app/foo', {'instances' => 9000 }, false)
148
- app.change!('instances' => 9000)
200
+ subject.change!('instances' => 9000)
149
201
  end
150
202
  end
151
203
 
152
204
  describe '#roll_back!' do
153
- let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
205
+ subject { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
154
206
 
155
207
  it 'checks for read only' do
156
208
  expect(subject).to receive(:check_read_only)
@@ -159,18 +211,18 @@ describe Marathon::App do
159
211
  end
160
212
 
161
213
  it 'changes the app' do
162
- expect(app).to receive(:change!).with({'version' => 'old_version' }, false)
163
- app.roll_back!('old_version')
214
+ expect(subject).to receive(:change!).with({'version' => 'old_version' }, false)
215
+ subject.roll_back!('old_version')
164
216
  end
165
217
 
166
218
  it 'changes the app with force' do
167
- expect(app).to receive(:change!).with({'version' => 'old_version' }, true)
168
- app.roll_back!('old_version', true)
219
+ expect(subject).to receive(:change!).with({'version' => 'old_version' }, true)
220
+ subject.roll_back!('old_version', true)
169
221
  end
170
222
  end
171
223
 
172
224
  describe '#scale!' do
173
- let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
225
+ subject { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
174
226
 
175
227
  it 'checks for read only' do
176
228
  expect(subject).to receive(:check_read_only)
@@ -179,18 +231,18 @@ describe Marathon::App do
179
231
  end
180
232
 
181
233
  it 'changes the app' do
182
- expect(app).to receive(:change!).with({'instances' => 9000 }, false)
183
- app.scale!(9000)
234
+ expect(subject).to receive(:change!).with({'instances' => 9000 }, false)
235
+ subject.scale!(9000)
184
236
  end
185
237
 
186
238
  it 'changes the app with force' do
187
- expect(app).to receive(:change!).with({'instances' => 9000 }, true)
188
- app.scale!(9000, true)
239
+ expect(subject).to receive(:change!).with({'instances' => 9000 }, true)
240
+ subject.scale!(9000, true)
189
241
  end
190
242
  end
191
243
 
192
244
  describe '#suspend!' do
193
- let(:app) { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
245
+ subject { described_class.new({ 'id' => '/app/foo', 'instances' => 10 }) }
194
246
 
195
247
  it 'checks for read only' do
196
248
  expect(subject).to receive(:check_read_only)
@@ -199,13 +251,13 @@ describe Marathon::App do
199
251
  end
200
252
 
201
253
  it 'scales the app to 0' do
202
- expect(app).to receive(:scale!).with(0, false)
203
- app.suspend!
254
+ expect(subject).to receive(:scale!).with(0, false)
255
+ subject.suspend!
204
256
  end
205
257
 
206
258
  it 'scales the app to 0 with force' do
207
- expect(app).to receive(:scale!).with(0, true)
208
- app.suspend!(true)
259
+ expect(subject).to receive(:scale!).with(0, true)
260
+ subject.suspend!(true)
209
261
  end
210
262
  end
211
263
 
@@ -216,17 +268,17 @@ describe Marathon::App do
216
268
  expect(Marathon.connection).to receive(:get)
217
269
  .with('/v2/apps', {:cmd => 'foo', :embed => 'apps.tasks'})
218
270
  .and_return({ 'apps' => [] })
219
- described_class.list('foo', 'apps.tasks')
271
+ subject.list('foo', 'apps.tasks')
220
272
  end
221
273
 
222
274
  it 'raises error when run with strange embed' do
223
275
  expect {
224
- described_class.list(nil, 'foo')
276
+ subject.list(nil, 'foo')
225
277
  }.to raise_error(Marathon::Error::ArgumentError)
226
278
  end
227
279
 
228
280
  it 'lists apps', :vcr do
229
- apps = described_class.list
281
+ apps = subject.list
230
282
  expect(apps.size).not_to eq(0)
231
283
  expect(apps.first).to be_instance_of(described_class)
232
284
  expect(apps.first.cpus).to eq(0.1)
@@ -238,7 +290,7 @@ describe Marathon::App do
238
290
  subject { described_class }
239
291
 
240
292
  it 'starts the app', :vcr do
241
- app = described_class.start({ :id => '/test', :cmd => 'sleep 10', :instances => 1, :cpus => 0.1, :mem => 32})
293
+ app = subject.start({ :id => '/test', :cmd => 'sleep 10', :instances => 1, :cpus => 0.1, :mem => 32})
242
294
  expect(app).to be_instance_of(described_class)
243
295
  expect(app.id).to eq('/test')
244
296
  expect(app.instances).to eq(1)
@@ -248,7 +300,7 @@ describe Marathon::App do
248
300
 
249
301
  it 'fails getting not existing app', :vcr do
250
302
  expect {
251
- described_class.get('fooo app')
303
+ subject.get('fooo app')
252
304
  }.to raise_error(Marathon::Error::NotFoundError)
253
305
  end
254
306
  end
@@ -257,7 +309,7 @@ describe Marathon::App do
257
309
  subject { described_class }
258
310
 
259
311
  it 'gets the app', :vcr do
260
- app = described_class.get('/ubuntu')
312
+ app = subject.get('/ubuntu')
261
313
  expect(app).to be_instance_of(described_class)
262
314
  expect(app.id).to eq('/ubuntu')
263
315
  expect(app.instances).to eq(1)
@@ -267,7 +319,7 @@ describe Marathon::App do
267
319
 
268
320
  it 'fails getting not existing app', :vcr do
269
321
  expect {
270
- described_class.get('fooo app')
322
+ subject.get('fooo app')
271
323
  }.to raise_error(Marathon::Error::NotFoundError)
272
324
  end
273
325
  end
@@ -276,12 +328,12 @@ describe Marathon::App do
276
328
  subject { described_class }
277
329
 
278
330
  it 'deletes the app', :vcr do
279
- described_class.delete('/ubuntu')
331
+ subject.delete('/ubuntu')
280
332
  end
281
333
 
282
334
  it 'fails deleting not existing app', :vcr do
283
335
  expect {
284
- described_class.delete('fooo app')
336
+ subject.delete('fooo app')
285
337
  }.to raise_error(Marathon::Error::NotFoundError)
286
338
  end
287
339
  end
@@ -289,9 +341,13 @@ describe Marathon::App do
289
341
  describe '.restart' do
290
342
  subject { described_class }
291
343
 
344
+ it 'restarts an app', :vcr do
345
+ expect(subject.restart('/ubuntu2')).to be_instance_of(Marathon::DeploymentInfo)
346
+ end
347
+
292
348
  it 'fails restarting not existing app', :vcr do
293
349
  expect {
294
- described_class.restart('fooo app')
350
+ subject.restart('fooo app')
295
351
  }.to raise_error(Marathon::Error::NotFoundError)
296
352
  end
297
353
  end
@@ -300,13 +356,15 @@ describe Marathon::App do
300
356
  subject { described_class }
301
357
 
302
358
  it 'changes the app', :vcr do
303
- described_class.change('/ubuntu2', { 'instances' => 2 })
304
- described_class.change('/ubuntu2', { 'instances' => 1 }, true)
359
+ expect(subject.change('/ubuntu2', { 'instances' => 2 }))
360
+ .to be_instance_of(Marathon::DeploymentInfo)
361
+ expect(subject.change('/ubuntu2', { 'instances' => 1 }, true))
362
+ .to be_instance_of(Marathon::DeploymentInfo)
305
363
  end
306
364
 
307
365
  it 'fails with stange attributes', :vcr do
308
366
  expect {
309
- described_class.change('/ubuntu2', { 'instances' => 'foo' })
367
+ subject.change('/ubuntu2', { 'instances' => 'foo' })
310
368
  }.to raise_error(Marathon::Error::ClientError)
311
369
  end
312
370
  end
@@ -331,4 +389,4 @@ describe Marathon::App do
331
389
  expect(version.read_only).to be(true)
332
390
  end
333
391
  end
334
- end
392
+ end