Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit b7ca083

Browse files
authored
Merge pull request cloudfoundry#1513 from cloudfoundry/deployment_cancel_link
Add link for deployment cancel action
2 parents 8e267a7 + 128916a commit b7ca083

7 files changed

Lines changed: 134 additions & 16 deletions

File tree

app/actions/deployment_cancel.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class << self
1111
def cancel(deployment:, user_audit_info:)
1212
deployment.db.transaction do
1313
deployment.lock!
14-
reject_invalid_state!(deployment) unless valid_state?(deployment)
14+
reject_invalid_state!(deployment) unless deployment.cancelable?
1515

1616
begin
1717
AppAssignDroplet.new(user_audit_info).assign(deployment.app, deployment.previous_droplet)
@@ -29,12 +29,6 @@ def cancel(deployment:, user_audit_info:)
2929

3030
private
3131

32-
def valid_state?(deployment)
33-
valid_states_for_cancel = [DeploymentModel::DEPLOYING_STATE,
34-
DeploymentModel::CANCELING_STATE]
35-
valid_states_for_cancel.include?(deployment.state)
36-
end
37-
3832
def reject_invalid_state!(deployment)
3933
raise InvalidStatus.new("Cannot cancel a deployment with status: #{deployment.status_value} and reason: #{deployment.status_reason}")
4034
end

app/models/runtime/deployment_model.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,11 @@ def deploying_count
7070
def deploying?
7171
state == DEPLOYING_STATE
7272
end
73+
74+
def cancelable?
75+
valid_states_for_cancel = [DeploymentModel::DEPLOYING_STATE,
76+
DeploymentModel::CANCELING_STATE]
77+
valid_states_for_cancel.include?(state)
78+
end
7379
end
7480
end

app/presenters/v3/deployment_presenter.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,14 @@ def build_links
6969
app: {
7070
href: url_builder.build_url(path: "/v3/apps/#{deployment.app.guid}")
7171
},
72-
}
72+
}.tap do |links|
73+
if deployment.cancelable?
74+
links[:cancel] = {
75+
href: url_builder.build_url(path: "/v3/deployments/#{deployment.guid}/actions/cancel"),
76+
method: 'POST'
77+
}
78+
end
79+
end
7380
end
7481
end
7582
end

docs/v3/source/includes/api_resources/_deployments.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
},
4747
"app": {
4848
"href": "https://api.example.org/v3/apps/305cea31-5a44-45ca-b51b-e89c7a8ef8b2"
49+
},
50+
"cancel": {
51+
"href": "https://api.example.org/v3/deployments/59c3d133-2b83-46f3-960e-7765a129aea4/actions/cancel",
52+
"method": "POST"
4953
}
5054
}
5155
}

spec/request/deployments_spec.rb

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@
8080
},
8181
'app' => {
8282
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
83+
},
84+
'cancel' => {
85+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
86+
'method' => 'POST'
8387
}
8488
}
8589
})
@@ -151,6 +155,10 @@
151155
},
152156
'app' => {
153157
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
158+
},
159+
'cancel' => {
160+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
161+
'method' => 'POST'
154162
}
155163
}
156164
})
@@ -229,6 +237,10 @@
229237
},
230238
'app' => {
231239
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
240+
},
241+
'cancel' => {
242+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
243+
'method' => 'POST'
232244
}
233245
}
234246
})
@@ -343,6 +355,10 @@
343355
},
344356
'app' => {
345357
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
358+
},
359+
'cancel' => {
360+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
361+
'method' => 'POST'
346362
}
347363
}
348364
})
@@ -416,6 +432,10 @@
416432
},
417433
'app' => {
418434
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
435+
},
436+
'cancel' => {
437+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
438+
'method' => 'POST'
419439
}
420440
}
421441
})
@@ -657,6 +677,10 @@
657677
},
658678
'app' => {
659679
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
680+
},
681+
'cancel' => {
682+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
683+
'method' => 'POST'
660684
}
661685
}
662686
})
@@ -713,6 +737,10 @@
713737
},
714738
'app' => {
715739
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
740+
},
741+
'cancel' => {
742+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
743+
'method' => 'POST'
716744
}
717745
}
718746
})
@@ -823,6 +851,10 @@
823851
},
824852
'app' => {
825853
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
854+
},
855+
'cancel' => {
856+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
857+
'method' => 'POST'
826858
}
827859
}
828860
})
@@ -881,6 +913,10 @@
881913
},
882914
'app' => {
883915
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
916+
},
917+
'cancel' => {
918+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
919+
'method' => 'POST'
884920
}
885921
}
886922
})
@@ -950,7 +986,7 @@
950986
status_reason: VCAP::CloudController::DeploymentModel::SUPERSEDED_STATUS_REASON)
951987
}
952988

953-
def json_for_deployment(deployment, app_model, droplet, state, status_value, status_reason)
989+
def json_for_deployment(deployment, app_model, droplet, state, status_value, status_reason, cancel_link=true)
954990
{
955991
guid: deployment.guid,
956992
state: state,
@@ -995,7 +1031,14 @@ def json_for_deployment(deployment, app_model, droplet, state, status_value, sta
9951031
href: "#{link_prefix}/v3/apps/#{app_model.guid}"
9961032
}
9971033
}
998-
}
1034+
}.tap do |json|
1035+
if cancel_link
1036+
json[:links][:cancel] = {
1037+
href: "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
1038+
method: 'POST'
1039+
}
1040+
end
1041+
end
9991042
end
10001043

10011044
it 'should list all deployments' do
@@ -1045,15 +1088,21 @@ def json_for_deployment(deployment, app_model, droplet, state, status_value, sta
10451088
json_for_deployment(deployment3, app3, droplet3,
10461089
VCAP::CloudController::DeploymentModel::DEPLOYED_STATE,
10471090
VCAP::CloudController::DeploymentModel::FINALIZED_STATUS_VALUE,
1048-
VCAP::CloudController::DeploymentModel::DEPLOYED_STATUS_REASON),
1091+
VCAP::CloudController::DeploymentModel::DEPLOYED_STATUS_REASON,
1092+
false
1093+
),
10491094
json_for_deployment(deployment4, app4, droplet4,
10501095
VCAP::CloudController::DeploymentModel::CANCELED_STATE,
10511096
VCAP::CloudController::DeploymentModel::FINALIZED_STATUS_VALUE,
1052-
VCAP::CloudController::DeploymentModel::CANCELED_STATUS_REASON),
1097+
VCAP::CloudController::DeploymentModel::CANCELED_STATUS_REASON,
1098+
false
1099+
),
10531100
json_for_deployment(deployment5, app5, droplet5,
10541101
VCAP::CloudController::DeploymentModel::DEPLOYED_STATE,
10551102
VCAP::CloudController::DeploymentModel::FINALIZED_STATUS_VALUE,
1056-
VCAP::CloudController::DeploymentModel::SUPERSEDED_STATUS_REASON),
1103+
VCAP::CloudController::DeploymentModel::SUPERSEDED_STATUS_REASON,
1104+
false
1105+
),
10571106
]
10581107
)
10591108
# because the user is a manager in the shared org, they have access to see the domain
@@ -1096,11 +1145,15 @@ def json_for_deployment(deployment, app_model, droplet, state, status_value, sta
10961145
json_for_deployment(deployment3, app3, droplet3,
10971146
VCAP::CloudController::DeploymentModel::DEPLOYED_STATE,
10981147
VCAP::CloudController::DeploymentModel::FINALIZED_STATUS_VALUE,
1099-
VCAP::CloudController::DeploymentModel::DEPLOYED_STATUS_REASON),
1148+
VCAP::CloudController::DeploymentModel::DEPLOYED_STATUS_REASON,
1149+
false
1150+
),
11001151
json_for_deployment(deployment5, app5, droplet5,
11011152
VCAP::CloudController::DeploymentModel::DEPLOYED_STATE,
11021153
VCAP::CloudController::DeploymentModel::FINALIZED_STATUS_VALUE,
1103-
VCAP::CloudController::DeploymentModel::SUPERSEDED_STATUS_REASON),
1154+
VCAP::CloudController::DeploymentModel::SUPERSEDED_STATUS_REASON,
1155+
false
1156+
)
11041157
]
11051158
)
11061159
# because the user is a manager in the shared org, they have access to see the domain
@@ -1283,6 +1336,10 @@ def json_for_deployment(deployment, app_model, droplet, state, status_value, sta
12831336
},
12841337
'app' => {
12851338
'href' => "#{link_prefix}/v3/apps/#{app_model.guid}"
1339+
},
1340+
'cancel' => {
1341+
'href' => "#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel",
1342+
'method' => 'POST'
12861343
}
12871344
}
12881345
},

spec/unit/models/runtime/deployment_model_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,31 @@ module VCAP::CloudController
7777
expect(deployment.deploying?).to be(false)
7878
end
7979
end
80+
81+
describe '#cancelable?' do
82+
it 'returns true if the deployment is DEPLOYING' do
83+
deployment.state = DeploymentModel::DEPLOYING_STATE
84+
85+
expect(deployment.cancelable?).to be(true)
86+
end
87+
88+
it 'returns false if the deployment is DEPLOYED' do
89+
deployment.state = DeploymentModel::DEPLOYED_STATE
90+
91+
expect(deployment.cancelable?).to be(false)
92+
end
93+
94+
it 'returns true if the deployment is CANCELING' do
95+
deployment.state = DeploymentModel::CANCELING_STATE
96+
97+
expect(deployment.cancelable?).to be(true)
98+
end
99+
100+
it 'returns false if the deployment is CANCELED' do
101+
deployment.state = DeploymentModel::CANCELED_STATE
102+
103+
expect(deployment.cancelable?).to be(false)
104+
end
105+
end
80106
end
81107
end

spec/unit/presenters/v3/deployment_presenter_spec.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ module VCAP::CloudController::Presenters::V3
77
let(:previous_droplet) { VCAP::CloudController::DropletModel.make }
88
let(:app) { VCAP::CloudController::AppModel.make }
99
let(:process) { VCAP::CloudController::ProcessModel.make(guid: 'deploying-process-guid', type: 'web-deployment-guid-type') }
10+
let(:deployment_state) { VCAP::CloudController::DeploymentModel::DEPLOYING_STATE }
1011
let!(:deployment) do
1112
VCAP::CloudController::DeploymentModelTestFactory.make(
1213
app: app,
1314
droplet: droplet,
1415
previous_droplet: previous_droplet,
1516
deploying_web_process: process,
1617
last_healthy_at: '2019-07-12 19:01:54',
17-
state: VCAP::CloudController::DeploymentModel::DEPLOYING_STATE,
18+
state: deployment_state,
1819
status_value: VCAP::CloudController::DeploymentModel::ACTIVE_STATUS_VALUE,
1920
status_reason: VCAP::CloudController::DeploymentModel::DEPLOYING_STATUS_REASON
2021
)
@@ -104,6 +105,29 @@ module VCAP::CloudController::Presenters::V3
104105
expect(result[:new_processes]).to eq([{ guid: 'deploying-process-guid', type: 'web-deployment-guid-type' }])
105106
end
106107
end
108+
109+
describe 'cancel link' do
110+
context 'when the deployment is cancelable' do
111+
let(:deployment_state) { VCAP::CloudController::DeploymentModel::DEPLOYING_STATE }
112+
113+
it 'presents the cancel link' do
114+
result = DeploymentPresenter.new(deployment).to_hash
115+
116+
expect(result[:links][:cancel][:href]).to eq("#{link_prefix}/v3/deployments/#{deployment.guid}/actions/cancel")
117+
expect(result[:links][:cancel][:method]).to eq('POST')
118+
end
119+
end
120+
121+
context 'when the deployment is NOT cancelable' do
122+
let(:deployment_state) { VCAP::CloudController::DeploymentModel::CANCELED_STATE }
123+
124+
it 'does NOT present the cancel link' do
125+
result = DeploymentPresenter.new(deployment).to_hash
126+
127+
expect(result[:links][:cancel]).to be_nil
128+
end
129+
end
130+
end
107131
end
108132
end
109133
end

0 commit comments

Comments
 (0)