bosh-director 1.5.0.pre.1148 → 1.5.0.pre.1152
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/bosh/director/api/controller.rb +21 -628
- data/lib/bosh/director/api/controllers/backups_controller.rb +15 -0
- data/lib/bosh/director/api/controllers/base_controller.rb +90 -0
- data/lib/bosh/director/api/controllers/deployments_controller.rb +264 -0
- data/lib/bosh/director/api/controllers/info_controller.rb +32 -0
- data/lib/bosh/director/api/controllers/packages_controller.rb +30 -0
- data/lib/bosh/director/api/controllers/releases_controller.rb +86 -0
- data/lib/bosh/director/api/controllers/resources_controller.rb +12 -0
- data/lib/bosh/director/api/controllers/resurrection_controller.rb +14 -0
- data/lib/bosh/director/api/controllers/stemcells_controller.rb +38 -0
- data/lib/bosh/director/api/controllers/tasks_controller.rb +95 -0
- data/lib/bosh/director/api/controllers/users_controller.rb +30 -0
- data/lib/bosh/director/version.rb +1 -1
- metadata +27 -16
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bosh/director/api/controllers/base_controller'
|
2
|
+
|
3
|
+
module Bosh::Director
|
4
|
+
module Api::Controllers
|
5
|
+
class BackupsController < BaseController
|
6
|
+
post '/backups' do
|
7
|
+
start_task { @backup_manager.create_backup(@user) }
|
8
|
+
end
|
9
|
+
|
10
|
+
get '/backups' do
|
11
|
+
send_file @backup_manager.destination_path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Bosh::Director
|
2
|
+
module Api
|
3
|
+
module Controllers
|
4
|
+
class BaseController < Sinatra::Base
|
5
|
+
PUBLIC_URLS = %w(/info)
|
6
|
+
|
7
|
+
include ApiHelper
|
8
|
+
include Http
|
9
|
+
include DnsHelper
|
10
|
+
|
11
|
+
def initialize(*_)
|
12
|
+
super
|
13
|
+
@deployment_manager = DeploymentManager.new
|
14
|
+
@backup_manager = BackupManager.new
|
15
|
+
@instance_manager = InstanceManager.new
|
16
|
+
@resurrector_manager = ResurrectorManager.new
|
17
|
+
@problem_manager = ProblemManager.new
|
18
|
+
@property_manager = PropertyManager.new
|
19
|
+
@resource_manager = ResourceManager.new
|
20
|
+
@release_manager = ReleaseManager.new
|
21
|
+
@snapshot_manager = SnapshotManager.new
|
22
|
+
@stemcell_manager = StemcellManager.new
|
23
|
+
@task_manager = TaskManager.new
|
24
|
+
@user_manager = UserManager.new
|
25
|
+
@vm_state_manager = VmStateManager.new
|
26
|
+
@logger = Config.logger
|
27
|
+
end
|
28
|
+
|
29
|
+
mime_type :tgz, 'application/x-compressed'
|
30
|
+
|
31
|
+
def self.consumes(*types)
|
32
|
+
types = Set.new(types)
|
33
|
+
types.map! { |t| mime_type(t) }
|
34
|
+
|
35
|
+
condition do
|
36
|
+
types.include?(request.content_type)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def authenticate(user, password)
|
41
|
+
if @user_manager.authenticate(user, password)
|
42
|
+
@user = user
|
43
|
+
true
|
44
|
+
else
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
helpers ControllerHelpers
|
50
|
+
|
51
|
+
before do
|
52
|
+
auth_provided = %w(HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION).detect do |key|
|
53
|
+
request.env.has_key?(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
protected! if auth_provided || !PUBLIC_URLS.include?(request.path_info)
|
57
|
+
end
|
58
|
+
|
59
|
+
after { headers('Date' => Time.now.rfc822) } # As thin doesn't inject date
|
60
|
+
|
61
|
+
configure do
|
62
|
+
set(:show_exceptions, false)
|
63
|
+
set(:raise_errors, false)
|
64
|
+
set(:dump_errors, false)
|
65
|
+
end
|
66
|
+
|
67
|
+
error do
|
68
|
+
exception = request.env['sinatra.error']
|
69
|
+
if exception.kind_of?(DirectorError)
|
70
|
+
@logger.debug('Request failed, ' +
|
71
|
+
"response code: #{exception.response_code}, " +
|
72
|
+
"error code: #{exception.error_code}, " +
|
73
|
+
"error message: #{exception.message}")
|
74
|
+
status(exception.response_code)
|
75
|
+
error_payload = {
|
76
|
+
'code' => exception.error_code,
|
77
|
+
'description' => exception.message
|
78
|
+
}
|
79
|
+
json_encode(error_payload)
|
80
|
+
else
|
81
|
+
msg = ["#{exception.class} - #{exception.message}:"]
|
82
|
+
msg.concat(exception.backtrace)
|
83
|
+
@logger.error(msg.join("\n"))
|
84
|
+
status(500)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'bosh/director/api/controllers/base_controller'
|
2
|
+
|
3
|
+
module Bosh::Director
|
4
|
+
module Api::Controllers
|
5
|
+
class DeploymentsController < BaseController
|
6
|
+
get '/deployments/:deployment/jobs/:job/:index' do
|
7
|
+
instance = @instance_manager.find_by_name(params[:deployment], params[:job], params[:index])
|
8
|
+
|
9
|
+
response = {
|
10
|
+
deployment: params[:deployment],
|
11
|
+
job: instance.job,
|
12
|
+
index: instance.index,
|
13
|
+
state: instance.state,
|
14
|
+
disks: instance.persistent_disks.map {|d| d.disk_cid}
|
15
|
+
}
|
16
|
+
|
17
|
+
json_encode(response)
|
18
|
+
end
|
19
|
+
|
20
|
+
# PUT /deployments/foo/jobs/dea?new_name=dea_new
|
21
|
+
put '/deployments/:deployment/jobs/:job', :consumes => :yaml do
|
22
|
+
if params['state']
|
23
|
+
options = {
|
24
|
+
'job_states' => {
|
25
|
+
params[:job] => {
|
26
|
+
'state' => params['state']
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
else
|
31
|
+
unless params['new_name']
|
32
|
+
raise DirectorError, "Missing operation on job `#{params[:job]}'"
|
33
|
+
end
|
34
|
+
options = {
|
35
|
+
'job_rename' => {
|
36
|
+
'old_name' => params[:job],
|
37
|
+
'new_name' => params['new_name']
|
38
|
+
}
|
39
|
+
}
|
40
|
+
options['job_rename']['force'] = true if params['force'] == 'true'
|
41
|
+
end
|
42
|
+
|
43
|
+
# we get the deployment here even though it isn't used here, to make sure
|
44
|
+
# the call returns a 404 if the deployment doesn't exist
|
45
|
+
@deployment_manager.find_by_name(params[:deployment])
|
46
|
+
task = @deployment_manager.create_deployment(@user, request.body, options)
|
47
|
+
redirect "/tasks/#{task.id}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# PUT /deployments/foo/jobs/dea/2?state={started,stopped,detached,restart,recreate}
|
51
|
+
put '/deployments/:deployment/jobs/:job/:index', :consumes => :yaml do
|
52
|
+
begin
|
53
|
+
index = Integer(params[:index])
|
54
|
+
rescue ArgumentError
|
55
|
+
raise InstanceInvalidIndex, "Invalid instance index `#{params[:index]}'"
|
56
|
+
end
|
57
|
+
|
58
|
+
options = {
|
59
|
+
'job_states' => {
|
60
|
+
params[:job] => {
|
61
|
+
'instance_states' => {
|
62
|
+
index => params['state']
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
69
|
+
manifest = request.content_length.nil? ? StringIO.new(deployment.manifest) : request.body
|
70
|
+
task = @deployment_manager.create_deployment(@user, manifest, options)
|
71
|
+
redirect "/tasks/#{task.id}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# GET /deployments/foo/jobs/dea/2/logs
|
75
|
+
get '/deployments/:deployment/jobs/:job/:index/logs' do
|
76
|
+
deployment = params[:deployment]
|
77
|
+
job = params[:job]
|
78
|
+
index = params[:index]
|
79
|
+
|
80
|
+
options = {
|
81
|
+
'type' => params[:type].to_s.strip,
|
82
|
+
'filters' => params[:filters].to_s.strip.split(/[\s\,]+/)
|
83
|
+
}
|
84
|
+
|
85
|
+
task = @instance_manager.fetch_logs(@user, deployment, job, index, options)
|
86
|
+
redirect "/tasks/#{task.id}"
|
87
|
+
end
|
88
|
+
|
89
|
+
get '/deployments/:deployment/snapshots' do
|
90
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
91
|
+
json_encode(@snapshot_manager.snapshots(deployment))
|
92
|
+
end
|
93
|
+
|
94
|
+
get '/deployments/:deployment/jobs/:job/:index/snapshots' do
|
95
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
96
|
+
json_encode(@snapshot_manager.snapshots(deployment, params[:job], params[:index]))
|
97
|
+
end
|
98
|
+
|
99
|
+
post '/deployments/:deployment/snapshots' do
|
100
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
101
|
+
# until we can tell the agent to flush and wait, all snapshots are considered dirty
|
102
|
+
options = {clean: false}
|
103
|
+
|
104
|
+
task = @snapshot_manager.create_deployment_snapshot_task(@user, deployment, options)
|
105
|
+
redirect "/tasks/#{task.id}"
|
106
|
+
end
|
107
|
+
|
108
|
+
put '/deployments/:deployment/jobs/:job/:index/resurrection', consumes: :json do
|
109
|
+
payload = json_decode(request.body)
|
110
|
+
|
111
|
+
@resurrector_manager.set_pause_for_instance(params[:deployment], params[:job], params[:index], payload['resurrection_paused'])
|
112
|
+
end
|
113
|
+
|
114
|
+
post '/deployments/:deployment/jobs/:job/:index/snapshots' do
|
115
|
+
instance = @instance_manager.find_by_name(params[:deployment], params[:job], params[:index])
|
116
|
+
# until we can tell the agent to flush and wait, all snapshots are considered dirty
|
117
|
+
options = {clean: false}
|
118
|
+
|
119
|
+
task = @snapshot_manager.create_snapshot_task(@user, instance, options)
|
120
|
+
redirect "/tasks/#{task.id}"
|
121
|
+
end
|
122
|
+
|
123
|
+
delete '/deployments/:deployment/snapshots' do
|
124
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
125
|
+
|
126
|
+
task = @snapshot_manager.delete_deployment_snapshots_task(@user, deployment)
|
127
|
+
redirect "/tasks/#{task.id}"
|
128
|
+
end
|
129
|
+
|
130
|
+
delete '/deployments/:deployment/snapshots/:cid' do
|
131
|
+
deployment = @deployment_manager.find_by_name(params[:deployment])
|
132
|
+
snapshot = @snapshot_manager.find_by_cid(deployment, params[:cid])
|
133
|
+
|
134
|
+
task = @snapshot_manager.delete_snapshots_task(@user, [params[:cid]])
|
135
|
+
redirect "/tasks/#{task.id}"
|
136
|
+
end
|
137
|
+
|
138
|
+
get '/deployments' do
|
139
|
+
deployments = Models::Deployment.order_by(:name.asc).map { |deployment|
|
140
|
+
name = deployment.name
|
141
|
+
|
142
|
+
releases = deployment.release_versions.map { |rv|
|
143
|
+
Hash['name', rv.release.name, 'version', rv.version.to_s]
|
144
|
+
}
|
145
|
+
|
146
|
+
stemcells = deployment.stemcells.map { |sc|
|
147
|
+
Hash['name', sc.name, 'version', sc.version]
|
148
|
+
}
|
149
|
+
|
150
|
+
Hash['name', name, 'releases', releases, 'stemcells', stemcells]
|
151
|
+
}
|
152
|
+
|
153
|
+
json_encode(deployments)
|
154
|
+
end
|
155
|
+
|
156
|
+
get '/deployments/:name' do
|
157
|
+
deployment = @deployment_manager.find_by_name(params[:name])
|
158
|
+
@deployment_manager.deployment_to_json(deployment)
|
159
|
+
end
|
160
|
+
|
161
|
+
get '/deployments/:name/vms' do
|
162
|
+
deployment = @deployment_manager.find_by_name(params[:name])
|
163
|
+
|
164
|
+
format = params[:format]
|
165
|
+
if format == 'full'
|
166
|
+
task = @vm_state_manager.fetch_vm_state(@user, deployment, format)
|
167
|
+
redirect "/tasks/#{task.id}"
|
168
|
+
else
|
169
|
+
@deployment_manager.deployment_vms_to_json(deployment)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
delete '/deployments/:name' do
|
174
|
+
deployment = @deployment_manager.find_by_name(params[:name])
|
175
|
+
|
176
|
+
options = {}
|
177
|
+
options['force'] = true if params['force'] == 'true'
|
178
|
+
options['keep_snapshots'] = true if params['keep_snapshots'] == 'true'
|
179
|
+
task = @deployment_manager.delete_deployment(@user, deployment, options)
|
180
|
+
redirect "/tasks/#{task.id}"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Property management
|
184
|
+
get '/deployments/:deployment/properties' do
|
185
|
+
properties = @property_manager.get_properties(params[:deployment]).map do |property|
|
186
|
+
{ 'name' => property.name, 'value' => property.value }
|
187
|
+
end
|
188
|
+
json_encode(properties)
|
189
|
+
end
|
190
|
+
|
191
|
+
get '/deployments/:deployment/properties/:property' do
|
192
|
+
property = @property_manager.get_property(params[:deployment], params[:property])
|
193
|
+
json_encode('value' => property.value)
|
194
|
+
end
|
195
|
+
|
196
|
+
post '/deployments/:deployment/properties', :consumes => [:json] do
|
197
|
+
payload = json_decode(request.body)
|
198
|
+
@property_manager.create_property(params[:deployment], payload['name'], payload['value'])
|
199
|
+
status(204)
|
200
|
+
end
|
201
|
+
|
202
|
+
post '/deployments/:deployment/ssh', :consumes => [:json] do
|
203
|
+
payload = json_decode(request.body)
|
204
|
+
task = @instance_manager.ssh(@user, payload)
|
205
|
+
redirect "/tasks/#{task.id}"
|
206
|
+
end
|
207
|
+
|
208
|
+
put '/deployments/:deployment/properties/:property', :consumes => [:json] do
|
209
|
+
payload = json_decode(request.body)
|
210
|
+
@property_manager.update_property(params[:deployment], params[:property], payload['value'])
|
211
|
+
status(204)
|
212
|
+
end
|
213
|
+
|
214
|
+
delete '/deployments/:deployment/properties/:property' do
|
215
|
+
@property_manager.delete_property(params[:deployment], params[:property])
|
216
|
+
status(204)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Cloud check
|
220
|
+
|
221
|
+
# Initiate deployment scan
|
222
|
+
post '/deployments/:deployment/scans' do
|
223
|
+
start_task { @problem_manager.perform_scan(@user, params[:deployment]) }
|
224
|
+
end
|
225
|
+
|
226
|
+
# Get the list of problems for a particular deployment
|
227
|
+
get '/deployments/:deployment/problems' do
|
228
|
+
problems = @problem_manager.get_problems(params[:deployment]).map do |problem|
|
229
|
+
{
|
230
|
+
'id' => problem.id,
|
231
|
+
'type' => problem.type,
|
232
|
+
'data' => problem.data,
|
233
|
+
'description' => problem.description,
|
234
|
+
'resolutions' => problem.resolutions
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
json_encode(problems)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Try to resolve a set of problems
|
242
|
+
put '/deployments/:deployment/problems', :consumes => [:json] do
|
243
|
+
payload = json_decode(request.body)
|
244
|
+
start_task { @problem_manager.apply_resolutions(@user, params[:deployment], payload['resolutions']) }
|
245
|
+
end
|
246
|
+
|
247
|
+
put '/deployments/:deployment/scan_and_fix', :consumes => :json do
|
248
|
+
jobs_json = json_decode(request.body)['jobs']
|
249
|
+
# payload: [['j1', 'i1'], ['j1', 'i2'], ['j2', 'i1'], ...]
|
250
|
+
payload = convert_job_instance_hash(jobs_json)
|
251
|
+
|
252
|
+
start_task { @problem_manager.scan_and_fix(@user, params[:deployment], payload) }
|
253
|
+
end
|
254
|
+
|
255
|
+
post '/deployments', :consumes => :yaml do
|
256
|
+
options = {}
|
257
|
+
options['recreate'] = true if params['recreate'] == 'true'
|
258
|
+
|
259
|
+
task = @deployment_manager.create_deployment(@user, request.body, options)
|
260
|
+
redirect "/tasks/#{task.id}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'bosh/director/api/controllers/base_controller'
|
2
|
+
|
3
|
+
module Bosh::Director
|
4
|
+
module Api::Controllers
|
5
|
+
class InfoController < BaseController
|
6
|
+
get '/info' do
|
7
|
+
status = {
|
8
|
+
'name' => Config.name,
|
9
|
+
'uuid' => Config.uuid,
|
10
|
+
'version' => "#{VERSION} (#{Config.revision})",
|
11
|
+
'user' => @user,
|
12
|
+
'cpi' => Config.cloud_type,
|
13
|
+
'features' => {
|
14
|
+
'dns' => {
|
15
|
+
'status' => Config.dns_enabled?,
|
16
|
+
'extras' => { 'domain_name' => dns_domain_name }
|
17
|
+
},
|
18
|
+
'compiled_package_cache' => {
|
19
|
+
'status' => Config.use_compiled_package_cache?,
|
20
|
+
'extras' => { 'provider' => Config.compiled_package_cache_provider }
|
21
|
+
},
|
22
|
+
'snapshots' => {
|
23
|
+
'status' => Config.enable_snapshots
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
content_type(:json)
|
28
|
+
json_encode(status)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bosh/director/api/controllers/base_controller'
|
2
|
+
|
3
|
+
module Bosh::Director
|
4
|
+
module Api::Controllers
|
5
|
+
class PackagesController < BaseController
|
6
|
+
post '/packages/matches', :consumes => :yaml do
|
7
|
+
manifest = Psych.load(request.body)
|
8
|
+
unless manifest.is_a?(Hash) && manifest['packages'].is_a?(Array)
|
9
|
+
raise BadManifest, "Manifest doesn't have a usable packages section"
|
10
|
+
end
|
11
|
+
|
12
|
+
fp_list = []
|
13
|
+
sha1_list = []
|
14
|
+
|
15
|
+
manifest['packages'].each do |package|
|
16
|
+
fp_list << package['fingerprint'] if package['fingerprint']
|
17
|
+
sha1_list << package['sha1'] if package['sha1']
|
18
|
+
end
|
19
|
+
|
20
|
+
filter = {:fingerprint => fp_list, :sha1 => sha1_list}.sql_or
|
21
|
+
|
22
|
+
result = Models::Package.where(filter).all.map { |package|
|
23
|
+
[package.sha1, package.fingerprint]
|
24
|
+
}.flatten.compact.uniq
|
25
|
+
|
26
|
+
json_encode(result)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'bosh/director/api/controllers/base_controller'
|
2
|
+
|
3
|
+
module Bosh::Director
|
4
|
+
module Api::Controllers
|
5
|
+
class ReleasesController < BaseController
|
6
|
+
post '/releases', :consumes => :tgz do
|
7
|
+
options = {}
|
8
|
+
options['remote'] = false
|
9
|
+
options['rebase'] = true if params['rebase'] == 'true'
|
10
|
+
|
11
|
+
task = @release_manager.create_release(@user, request.body, options)
|
12
|
+
redirect "/tasks/#{task.id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
post '/releases', :consumes => :json do
|
16
|
+
options = {}
|
17
|
+
options['remote'] = true
|
18
|
+
options['rebase'] = true if params['rebase'] == 'true'
|
19
|
+
payload = json_decode(request.body)
|
20
|
+
|
21
|
+
task = @release_manager.create_release(@user, payload['location'], options)
|
22
|
+
redirect "/tasks/#{task.id}"
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/releases' do
|
26
|
+
releases = Models::Release.order_by(:name.asc).map do |release|
|
27
|
+
release_versions = release.versions_dataset.order_by(:version.asc).map do |rv|
|
28
|
+
Hash['version', rv.version.to_s,
|
29
|
+
'commit_hash', rv.commit_hash,
|
30
|
+
'uncommitted_changes', rv.uncommitted_changes,
|
31
|
+
'currently_deployed', !rv.deployments.empty?,
|
32
|
+
'job_names', rv.templates.map(&:name)]
|
33
|
+
end
|
34
|
+
|
35
|
+
Hash['name', release.name,
|
36
|
+
'release_versions', release_versions]
|
37
|
+
end
|
38
|
+
|
39
|
+
json_encode(releases)
|
40
|
+
end
|
41
|
+
|
42
|
+
get '/releases/:name' do
|
43
|
+
name = params[:name].to_s.strip
|
44
|
+
release = @release_manager.find_by_name(name)
|
45
|
+
|
46
|
+
result = { }
|
47
|
+
|
48
|
+
result['packages'] = release.packages.map do |package|
|
49
|
+
{
|
50
|
+
'name' => package.name,
|
51
|
+
'sha1' => package.sha1,
|
52
|
+
'version' => package.version.to_s,
|
53
|
+
'dependencies' => package.dependency_set.to_a
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
result['jobs'] = release.templates.map do |template|
|
58
|
+
{
|
59
|
+
'name' => template.name,
|
60
|
+
'sha1' => template.sha1,
|
61
|
+
'version' => template.version.to_s,
|
62
|
+
'packages' => template.package_names
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
result['versions'] = release.versions.map do |rv|
|
67
|
+
rv.version.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
content_type(:json)
|
71
|
+
json_encode(result)
|
72
|
+
end
|
73
|
+
|
74
|
+
delete '/releases/:name' do
|
75
|
+
release = @release_manager.find_by_name(params[:name])
|
76
|
+
|
77
|
+
options = {}
|
78
|
+
options['force'] = true if params['force'] == 'true'
|
79
|
+
options['version'] = params['version']
|
80
|
+
|
81
|
+
task = @release_manager.delete_release(@user, release, options)
|
82
|
+
redirect "/tasks/#{task.id}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|