Skip to content

Commit 1cf84a0

Browse files
authored
Fix issue with displaying Tasks that no longer have a Droplet (#4469)
- Although a Task must have a Droplet associated with it when it is created, these Droplets will be eventually cleaned up as the app is pushed with newer code over time. This change updates the presenter to return null when a Task's user can no longer be looked up because its droplet has been deleted. - Fixes #4467
1 parent 1ee2ea6 commit 1cf84a0

File tree

4 files changed

+129
-8
lines changed

4 files changed

+129
-8
lines changed

app/models/runtime/task_model.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,13 @@ def run_action_user
5656
end
5757
end
5858

59-
delegate :docker?, to: :droplet
60-
delegate :cnb?, to: :droplet
59+
def docker?
60+
!!droplet&.docker?
61+
end
62+
63+
def cnb?
64+
!!droplet&.cnb?
65+
end
6166

6267
private
6368

app/presenters/v3/task_presenter.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def to_hash
1515
sequence_id: task.sequence_id,
1616
name: task.name,
1717
command: task.command,
18-
user: task.run_action_user,
18+
user: presented_user(task),
1919
state: task.state,
2020
memory_in_mb: task.memory_in_mb,
2121
disk_in_mb: task.disk_in_mb,
@@ -37,6 +37,14 @@ def task
3737
@resource
3838
end
3939

40+
def presented_user(task)
41+
if task.droplet.present?
42+
task.run_action_user
43+
else
44+
task.user
45+
end
46+
end
47+
4048
def build_links
4149
{
4250
self: { href: url_builder.build_url(path: "/v3/tasks/#{task.guid}") },

spec/unit/models/runtime/task_model_spec.rb

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,12 @@ module VCAP::CloudController
166166

167167
describe '#run_action_user' do
168168
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
169-
let(:droplet) { DropletModel.make }
169+
let(:droplet) { DropletModel.make(app: parent_app) }
170170

171171
context 'when the task belongs to a CNB lifecycle app' do
172172
let(:parent_app) { AppModel.make(:cnb) }
173173
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
174-
let(:droplet) { DropletModel.make(:cnb) }
174+
let(:droplet) { DropletModel.make(:cnb, app: parent_app) }
175175

176176
context 'when the task has a user specified' do
177177
before do
@@ -197,8 +197,8 @@ module VCAP::CloudController
197197
context 'when the task belongs to a Docker lifecycle app' do
198198
let(:parent_app) { AppModel.make(:docker) }
199199
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
200-
let(:droplet) { DropletModel.make(:docker) }
201-
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"cnb"}' }
200+
let(:droplet) { DropletModel.make(:docker, app: parent_app) }
201+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"some-user"}' }
202202

203203
before do
204204
task.droplet.update(execution_metadata: droplet_execution_metadata)
@@ -216,7 +216,7 @@ module VCAP::CloudController
216216

217217
context 'when the droplet execution metadata specifies a user' do
218218
it 'returns the specified user' do
219-
expect(task.run_action_user).to eq('cnb')
219+
expect(task.run_action_user).to eq('some-user')
220220
end
221221
end
222222

@@ -268,6 +268,91 @@ module VCAP::CloudController
268268
it 'returns the default "vcap" user' do
269269
expect(task.run_action_user).to eq('vcap')
270270
end
271+
272+
context 'when there is no droplet for the task' do
273+
before do
274+
task.droplet.delete
275+
task.reload
276+
end
277+
278+
it 'returns vcap' do
279+
expect(task.run_action_user).to eq('vcap')
280+
end
281+
end
282+
end
283+
end
284+
end
285+
286+
describe 'docker?' do
287+
context 'when there is a droplet and it has the docker lifecycle' do
288+
let(:parent_app) { AppModel.make(:docker) }
289+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
290+
let(:droplet) { DropletModel.make(:docker, app: parent_app) }
291+
292+
it 'returns true' do
293+
expect(task.docker?).to be(true)
294+
end
295+
end
296+
297+
context 'when there is a droplet and it does not have the docker lifecycle' do
298+
let(:parent_app) { AppModel.make(:docker) }
299+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
300+
let(:droplet) { DropletModel.make(:buildpack, app: parent_app) }
301+
302+
it 'returns false' do
303+
expect(task.docker?).to be(false)
304+
end
305+
end
306+
307+
context 'when there is not a droplet for the task' do
308+
let(:parent_app) { AppModel.make(:docker) }
309+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
310+
let(:droplet) { DropletModel.make(:docker, app: parent_app) }
311+
312+
before do
313+
task.droplet.delete
314+
task.reload
315+
end
316+
317+
it 'returns false' do
318+
expect(task.docker?).to be(false)
319+
end
320+
end
321+
end
322+
323+
describe 'cnb?' do
324+
context 'when there is a droplet and it has the cnb lifecycle' do
325+
let(:parent_app) { AppModel.make(:cnb) }
326+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
327+
let(:droplet) { DropletModel.make(:cnb, app: parent_app) }
328+
329+
it 'returns true' do
330+
expect(task.cnb?).to be(true)
331+
end
332+
end
333+
334+
context 'when there is a droplet and it does not have the cnb lifecycle' do
335+
let(:parent_app) { AppModel.make(:cnb) }
336+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
337+
let(:droplet) { DropletModel.make(:buildpack, app: parent_app) }
338+
339+
it 'returns false' do
340+
expect(task.cnb?).to be(false)
341+
end
342+
end
343+
344+
context 'when there is not a droplet for the task' do
345+
let(:parent_app) { AppModel.make(:cnb) }
346+
let(:task) { TaskModel.make(app: parent_app, droplet: droplet, state: TaskModel::SUCCEEDED_STATE) }
347+
let(:droplet) { DropletModel.make(:cnb, app: parent_app) }
348+
349+
before do
350+
task.droplet.delete
351+
task.reload
352+
end
353+
354+
it 'returns false' do
355+
expect(task.cnb?).to be(false)
271356
end
272357
end
273358
end

spec/unit/presenters/v3/task_presenter_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
module VCAP::CloudController::Presenters::V3
55
RSpec.describe TaskPresenter do
66
subject(:presenter) { TaskPresenter.new(task) }
7+
let(:task_user) { nil }
78
let(:task) do
89
task = VCAP::CloudController::TaskModel.make(
910
failure_reason: 'sup dawg',
11+
user: task_user,
1012
memory_in_mb: 2048,
1113
disk_in_mb: 4048,
1214
log_rate_limit: 1024,
@@ -83,6 +85,27 @@ module VCAP::CloudController::Presenters::V3
8385
expect(result).not_to have_key(:command)
8486
end
8587
end
88+
89+
describe 'user' do
90+
context 'when the droplet for the task has been deleted' do
91+
before do
92+
task.droplet.delete
93+
task.reload
94+
end
95+
96+
it 'returns the user as nil' do
97+
expect(result[:user]).to be_nil
98+
end
99+
100+
context 'when the task has an explicit user set' do
101+
let(:task_user) { 'TestUser' }
102+
103+
it 'returns the user' do
104+
expect(result[:user]).to eq('TestUser')
105+
end
106+
end
107+
end
108+
end
86109
end
87110
end
88111
end

0 commit comments

Comments
 (0)