nucleus 0.2.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +9 -0
- data/README.md +43 -72
- data/lib/nucleus/adapter_resolver.rb +3 -3
- data/lib/nucleus/adapters/base_adapter.rb +109 -109
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -111
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -141
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -97
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +5 -5
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -41
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +6 -6
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -33
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +6 -6
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -80
- data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -57
- data/lib/nucleus/adapters/v1/heroku/data.rb +78 -78
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -146
- data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -51
- data/lib/nucleus/adapters/v1/heroku/logs.rb +2 -2
- data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -42
- data/lib/nucleus/adapters/v1/heroku/services.rb +168 -168
- data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -65
- data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -68
- data/lib/nucleus/adapters/v1/openshift_v2/application.rb +1 -1
- data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -96
- data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -60
- data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -106
- data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -125
- data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -58
- data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -173
- data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -49
- data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -464
- data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -44
- data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -53
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +3 -3
- data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -95
- data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -36
- data/lib/nucleus/core/adapter_extensions/http_client.rb +5 -5
- data/lib/nucleus/core/common/files/archive_extractor.rb +1 -1
- data/lib/nucleus/core/common/files/archiver.rb +2 -2
- data/lib/nucleus/core/file_handling/file_manager.rb +64 -64
- data/lib/nucleus/core/file_handling/git_deployer.rb +133 -133
- data/lib/nucleus/core/import/adapter_configuration.rb +53 -53
- data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -26
- data/lib/nucleus/version.rb +1 -1
- data/nucleus.gemspec +2 -2
- data/spec/integration/api/auth_spec.rb +3 -3
- data/spec/spec_helper.rb +98 -98
- data/spec/test_suites.rake +1 -1
- data/spec/unit/adapters/git_deployer_spec.rb +262 -262
- data/spec/unit/common/helpers/auth_helper_spec.rb +1 -1
- data/tasks/evaluation.rake +1 -1
- data/wiki/adapter_tests.md +0 -7
- data/wiki/implement_new_adapter.md +1 -1
- metadata +4 -20
- data/config/adapters/cloud_control.yml +0 -32
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +0 -108
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +0 -27
- data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +0 -23
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +0 -153
- data/lib/nucleus/adapters/v1/cloud_control/data.rb +0 -76
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +0 -68
- data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +0 -27
- data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +0 -71
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +0 -103
- data/lib/nucleus/adapters/v1/cloud_control/regions.rb +0 -32
- data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +0 -17
- data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +0 -31
- data/lib/nucleus/adapters/v1/cloud_control/services.rb +0 -162
- data/lib/nucleus/adapters/v1/cloud_control/token.rb +0 -17
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +0 -88
@@ -1,51 +1,51 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
module Lifecycle
|
6
|
-
# Lifecycle:
|
7
|
-
# A) via maintenance - workers are still active
|
8
|
-
# B) via formation - loose scaling information
|
9
|
-
|
10
|
-
# @see Stub#start
|
11
|
-
def start(application_id)
|
12
|
-
log.debug "Start @ #{@endpoint_url}"
|
13
|
-
|
14
|
-
app = application(application_id)
|
15
|
-
if app[:state] == Enums::ApplicationStates::DEPLOYED
|
16
|
-
# add web dyno if there currently are no dynos (state == deployed)
|
17
|
-
scale_web(application_id, 1)
|
18
|
-
elsif app[:state] == Enums::ApplicationStates::CREATED
|
19
|
-
# fail if there is no deployment
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
scale_worker(application_id, 1)
|
24
|
-
update_application(application_id, maintenance: false)
|
25
|
-
end
|
26
|
-
|
27
|
-
# @see Stub#stop
|
28
|
-
def stop(application_id)
|
29
|
-
log.debug "Stop @ #{@endpoint_url}"
|
30
|
-
|
31
|
-
# fail if there is no deployment
|
32
|
-
app = application(application_id)
|
33
|
-
if app[:state] == Enums::ApplicationStates::CREATED
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
scale_worker(application_id, 0)
|
38
|
-
update_application(application_id, maintenance: true)
|
39
|
-
end
|
40
|
-
|
41
|
-
# @see Stub#restart
|
42
|
-
def restart(application_id)
|
43
|
-
log.debug "Restart @ #{@endpoint_url}"
|
44
|
-
stop(application_id)
|
45
|
-
start(application_id)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
module Lifecycle
|
6
|
+
# Lifecycle:
|
7
|
+
# A) via maintenance - workers are still active
|
8
|
+
# B) via formation - loose scaling information
|
9
|
+
|
10
|
+
# @see Stub#start
|
11
|
+
def start(application_id)
|
12
|
+
log.debug "Start @ #{@endpoint_url}"
|
13
|
+
|
14
|
+
app = application(application_id)
|
15
|
+
if app[:state] == Enums::ApplicationStates::DEPLOYED
|
16
|
+
# add web dyno if there currently are no dynos (state == deployed)
|
17
|
+
scale_web(application_id, 1)
|
18
|
+
elsif app[:state] == Enums::ApplicationStates::CREATED
|
19
|
+
# fail if there is no deployment
|
20
|
+
raise Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be started'
|
21
|
+
end
|
22
|
+
|
23
|
+
scale_worker(application_id, 1)
|
24
|
+
update_application(application_id, maintenance: false)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @see Stub#stop
|
28
|
+
def stop(application_id)
|
29
|
+
log.debug "Stop @ #{@endpoint_url}"
|
30
|
+
|
31
|
+
# fail if there is no deployment
|
32
|
+
app = application(application_id)
|
33
|
+
if app[:state] == Enums::ApplicationStates::CREATED
|
34
|
+
raise Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be stopped'
|
35
|
+
end
|
36
|
+
|
37
|
+
scale_worker(application_id, 0)
|
38
|
+
update_application(application_id, maintenance: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see Stub#restart
|
42
|
+
def restart(application_id)
|
43
|
+
log.debug "Restart @ #{@endpoint_url}"
|
44
|
+
stop(application_id)
|
45
|
+
start(application_id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -33,8 +33,8 @@ module Nucleus
|
|
33
33
|
# @see Stub#log_entries
|
34
34
|
def log_entries(application_id, log_id)
|
35
35
|
unless log?(application_id, log_id)
|
36
|
-
|
37
|
-
|
36
|
+
raise Errors::AdapterResourceNotFoundError,
|
37
|
+
"Invalid log file '#{log_id}', not available for application '#{application_id}'"
|
38
38
|
end
|
39
39
|
|
40
40
|
return build_log_entries(application_id) if log_id.to_sym == Enums::ApplicationLogfileType::BUILD
|
@@ -1,42 +1,42 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
module Regions
|
6
|
-
# @see Stub#regions
|
7
|
-
def regions
|
8
|
-
response = get('/regions').body
|
9
|
-
response.each do |region|
|
10
|
-
region[:id] = region.delete(:name).upcase
|
11
|
-
end
|
12
|
-
response
|
13
|
-
end
|
14
|
-
|
15
|
-
# @see Stub#region
|
16
|
-
def region(region_name)
|
17
|
-
found_region = native_region(region_name)
|
18
|
-
|
19
|
-
|
20
|
-
found_region[:id] = found_region.delete(:name).upcase
|
21
|
-
found_region
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def retrieve_region(application)
|
27
|
-
return unless application.key?(:region)
|
28
|
-
found_region = native_region(application[:region])
|
29
|
-
|
30
|
-
|
31
|
-
application[:region] = found_region[:id]
|
32
|
-
end
|
33
|
-
|
34
|
-
def native_region(region_name)
|
35
|
-
response = get('/regions').body
|
36
|
-
response.find { |region| region[:name].casecmp(region_name) == 0 }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
module Regions
|
6
|
+
# @see Stub#regions
|
7
|
+
def regions
|
8
|
+
response = get('/regions').body
|
9
|
+
response.each do |region|
|
10
|
+
region[:id] = region.delete(:name).upcase
|
11
|
+
end
|
12
|
+
response
|
13
|
+
end
|
14
|
+
|
15
|
+
# @see Stub#region
|
16
|
+
def region(region_name)
|
17
|
+
found_region = native_region(region_name)
|
18
|
+
raise Errors::AdapterResourceNotFoundError,
|
19
|
+
"Region '#{region_name}' does not exist at the endpoint" if found_region.nil?
|
20
|
+
found_region[:id] = found_region.delete(:name).upcase
|
21
|
+
found_region
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def retrieve_region(application)
|
27
|
+
return unless application.key?(:region)
|
28
|
+
found_region = native_region(application[:region])
|
29
|
+
raise Errors::SemanticAdapterRequestError,
|
30
|
+
"Region '#{application[:region]}' does not exist at the endpoint" if found_region.nil?
|
31
|
+
application[:region] = found_region[:id]
|
32
|
+
end
|
33
|
+
|
34
|
+
def native_region(region_name)
|
35
|
+
response = get('/regions').body
|
36
|
+
response.find { |region| region[:name].casecmp(region_name) == 0 }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,168 +1,168 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
module Services
|
6
|
-
# @see Stub#services
|
7
|
-
def services
|
8
|
-
get('/addon-services').body.collect { |service| to_nucleus_service(service) }
|
9
|
-
end
|
10
|
-
|
11
|
-
# @see Stub#service
|
12
|
-
def service(service_id)
|
13
|
-
to_nucleus_service(get("/addon-services/#{service_id}").body)
|
14
|
-
end
|
15
|
-
|
16
|
-
# @see Stub#service_plans
|
17
|
-
def service_plans(service_id)
|
18
|
-
load_plans(service_id).collect { |plan| to_nucleus_plan(plan) }.sort_by do |plan|
|
19
|
-
# only compare the first cost, covers most cases and sorting for all costs would be far too complex
|
20
|
-
plan[:costs][0][:price].first[:amount].to_f
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# @see Stub#service_plan
|
25
|
-
def service_plan(service_id, plan_id)
|
26
|
-
to_nucleus_plan(get("/addon-services/#{service_id}/plans/#{plan_id}").body)
|
27
|
-
end
|
28
|
-
|
29
|
-
# @see Stub#installed_services
|
30
|
-
def installed_services(application_id)
|
31
|
-
get("/apps/#{application_id}/addons").body.collect { |service| to_nucleus_installed_service(service) }
|
32
|
-
end
|
33
|
-
|
34
|
-
# @see Stub#installed_service
|
35
|
-
def installed_service(application_id, service_id)
|
36
|
-
assigned_service = raw_installed_service(application_id, service_id)
|
37
|
-
to_nucleus_installed_service(assigned_service)
|
38
|
-
end
|
39
|
-
|
40
|
-
# @see Stub#add_service
|
41
|
-
def add_service(application_id, service_entity, plan_entity)
|
42
|
-
begin
|
43
|
-
# make sure plan belongs to this service, throws 404 if no such plan
|
44
|
-
# the service plan itself requires the name, e.g. 'sandbox' or the UUID
|
45
|
-
service_plan(service_entity[:id], plan_entity[:id])
|
46
|
-
rescue Errors::AdapterResourceNotFoundError => e
|
47
|
-
# convert to 422
|
48
|
-
raise Errors::SemanticAdapterRequestError, e.message
|
49
|
-
end
|
50
|
-
# the plan to choose requires the UUID of the plan OR the combination of both names
|
51
|
-
plan_id = service_plan_identifier(service_entity[:id], plan_entity[:id])
|
52
|
-
created = post("/apps/#{application_id}/addons", body: { plan: plan_id }).body
|
53
|
-
to_nucleus_installed_service(created)
|
54
|
-
end
|
55
|
-
|
56
|
-
# @see Stub#change_service
|
57
|
-
def change_service(application_id, service_id, plan_entity)
|
58
|
-
# make sure service is bound to the application
|
59
|
-
assignment_id = raw_installed_service(application_id, service_id)[:id]
|
60
|
-
begin
|
61
|
-
# make sure plan belongs to this service, throws 404 if no such plan
|
62
|
-
# the service plan itself requires the name, e.g. 'sandbox' or the UUID
|
63
|
-
service_plan(service_id, plan_entity[:id])
|
64
|
-
rescue Errors::AdapterResourceNotFoundError => e
|
65
|
-
# convert to 422
|
66
|
-
raise Errors::SemanticAdapterRequestError, e.message
|
67
|
-
end
|
68
|
-
# the plan to choose requires the UUID of the plan OR the combination of both names
|
69
|
-
plan_id = service_plan_identifier(service_id, plan_id)
|
70
|
-
updated = patch("/apps/#{application_id}/addons/#{assignment_id}", body: { plan: plan_id }).body
|
71
|
-
to_nucleus_installed_service(updated)
|
72
|
-
end
|
73
|
-
|
74
|
-
# @see Stub#remove_service
|
75
|
-
def remove_service(application_id, service_id)
|
76
|
-
# make sure service is bound to the application
|
77
|
-
assignment_id = raw_installed_service(application_id, service_id)[:id]
|
78
|
-
delete("/apps/#{application_id}/addons/#{assignment_id}")
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def service_plan_identifier(service_id, plan_id)
|
84
|
-
# process plan id_or_name to build the unique identifier
|
85
|
-
# a) is a UUID
|
86
|
-
return plan_id if Regexp::UUID_PATTERN =~ plan_id
|
87
|
-
# b) is valid identifier, contains ':'
|
88
|
-
return plan_id if /^[-\w]+:[-\w]+$/i =~ plan_id
|
89
|
-
# c) fetch id for name
|
90
|
-
return "#{service_id}:#{plan_id}" unless Regexp::UUID_PATTERN =~ service_id
|
91
|
-
# arriving here, service_id is UUID but plan_id is the name --> DOH!
|
92
|
-
# we return the plan_id and the request will presumably fail
|
93
|
-
plan_id
|
94
|
-
end
|
95
|
-
|
96
|
-
def raw_installed_service(application_id, service_id)
|
97
|
-
# here we probably receive the ID of the service, not the service assignment ID itself
|
98
|
-
installed = get("/apps/#{application_id}/addons/#{service_id}", expects: [200, 404])
|
99
|
-
if installed.status == 404
|
100
|
-
assignment_id = service_assignment_id(application_id, service_id)
|
101
|
-
|
102
|
-
|
103
|
-
return get("/apps/#{application_id}/addons/#{assignment_id}").body
|
104
|
-
end
|
105
|
-
installed.body
|
106
|
-
end
|
107
|
-
|
108
|
-
def service_assignment_id(application_id, service_id)
|
109
|
-
all_services = get("/apps/#{application_id}/addons").body
|
110
|
-
match = all_services.find do |addon|
|
111
|
-
addon[:addon_service][:id] == service_id || addon[:addon_service][:name] == service_id
|
112
|
-
end
|
113
|
-
return match[:id] if match
|
114
|
-
nil
|
115
|
-
end
|
116
|
-
|
117
|
-
def to_nucleus_service(service)
|
118
|
-
service[:description] = service.delete(:human_name)
|
119
|
-
service[:release] = service.delete(:state)
|
120
|
-
service[:required_services] = []
|
121
|
-
service[:free_plan] = free_plan?(service[:id])
|
122
|
-
service[:documentation_url] = "https://addons.heroku.com/#{service[:name]}"
|
123
|
-
service
|
124
|
-
end
|
125
|
-
|
126
|
-
def to_nucleus_installed_service(installed_service)
|
127
|
-
service = service(installed_service[:addon_service][:id])
|
128
|
-
# get all variables and reject all that do not belong to the addon
|
129
|
-
unless installed_service[:config_vars].nil? && installed_service[:config_vars].empty?
|
130
|
-
vars = get("/apps/#{installed_service[:app][:id]}/config-vars").body
|
131
|
-
# ignore all vars that do not belong to the service
|
132
|
-
vars = vars.delete_if { |k| !installed_service[:config_vars].include?(k.to_s) }
|
133
|
-
# format to desired format
|
134
|
-
vars = vars.collect { |k, v| { key: k, value: v, description: nil } }
|
135
|
-
end
|
136
|
-
service[:properties] = vars ? vars : []
|
137
|
-
service[:active_plan] = installed_service[:plan][:id]
|
138
|
-
service[:web_url] = installed_service[:web_url]
|
139
|
-
service
|
140
|
-
end
|
141
|
-
|
142
|
-
def to_nucleus_plan(plan)
|
143
|
-
# TODO: extract payment period to enum
|
144
|
-
plan[:costs] = [{ price: [amount: plan[:price][:cents] / 100.0, currency: 'USD'],
|
145
|
-
period: plan[:price][:unit], per_instance: false }]
|
146
|
-
plan[:free] = plan[:price][:cents] == 0
|
147
|
-
plan
|
148
|
-
end
|
149
|
-
|
150
|
-
def load_plans(service_id)
|
151
|
-
get("/addon-services/#{service_id}/plans").body
|
152
|
-
end
|
153
|
-
|
154
|
-
# Memoize this detection.
|
155
|
-
# The information is not critical, but takes some time to evaluate.
|
156
|
-
# Values are not expected to change often.
|
157
|
-
def free_plan?(service_id, plans = nil)
|
158
|
-
@free_plans ||= {}
|
159
|
-
return @free_plans[service_id] if @free_plans.key?(service_id)
|
160
|
-
plans = load_plans(service_id) unless plans
|
161
|
-
@free_plans[service_id] = plans.any? { |plan| plan[:price][:cents] == 0 }
|
162
|
-
@free_plans[service_id]
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
module Services
|
6
|
+
# @see Stub#services
|
7
|
+
def services
|
8
|
+
get('/addon-services').body.collect { |service| to_nucleus_service(service) }
|
9
|
+
end
|
10
|
+
|
11
|
+
# @see Stub#service
|
12
|
+
def service(service_id)
|
13
|
+
to_nucleus_service(get("/addon-services/#{service_id}").body)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see Stub#service_plans
|
17
|
+
def service_plans(service_id)
|
18
|
+
load_plans(service_id).collect { |plan| to_nucleus_plan(plan) }.sort_by do |plan|
|
19
|
+
# only compare the first cost, covers most cases and sorting for all costs would be far too complex
|
20
|
+
plan[:costs][0][:price].first[:amount].to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @see Stub#service_plan
|
25
|
+
def service_plan(service_id, plan_id)
|
26
|
+
to_nucleus_plan(get("/addon-services/#{service_id}/plans/#{plan_id}").body)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see Stub#installed_services
|
30
|
+
def installed_services(application_id)
|
31
|
+
get("/apps/#{application_id}/addons").body.collect { |service| to_nucleus_installed_service(service) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# @see Stub#installed_service
|
35
|
+
def installed_service(application_id, service_id)
|
36
|
+
assigned_service = raw_installed_service(application_id, service_id)
|
37
|
+
to_nucleus_installed_service(assigned_service)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @see Stub#add_service
|
41
|
+
def add_service(application_id, service_entity, plan_entity)
|
42
|
+
begin
|
43
|
+
# make sure plan belongs to this service, throws 404 if no such plan
|
44
|
+
# the service plan itself requires the name, e.g. 'sandbox' or the UUID
|
45
|
+
service_plan(service_entity[:id], plan_entity[:id])
|
46
|
+
rescue Errors::AdapterResourceNotFoundError => e
|
47
|
+
# convert to 422
|
48
|
+
raise Errors::SemanticAdapterRequestError, e.message
|
49
|
+
end
|
50
|
+
# the plan to choose requires the UUID of the plan OR the combination of both names
|
51
|
+
plan_id = service_plan_identifier(service_entity[:id], plan_entity[:id])
|
52
|
+
created = post("/apps/#{application_id}/addons", body: { plan: plan_id }).body
|
53
|
+
to_nucleus_installed_service(created)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @see Stub#change_service
|
57
|
+
def change_service(application_id, service_id, plan_entity)
|
58
|
+
# make sure service is bound to the application
|
59
|
+
assignment_id = raw_installed_service(application_id, service_id)[:id]
|
60
|
+
begin
|
61
|
+
# make sure plan belongs to this service, throws 404 if no such plan
|
62
|
+
# the service plan itself requires the name, e.g. 'sandbox' or the UUID
|
63
|
+
service_plan(service_id, plan_entity[:id])
|
64
|
+
rescue Errors::AdapterResourceNotFoundError => e
|
65
|
+
# convert to 422
|
66
|
+
raise Errors::SemanticAdapterRequestError, e.message
|
67
|
+
end
|
68
|
+
# the plan to choose requires the UUID of the plan OR the combination of both names
|
69
|
+
plan_id = service_plan_identifier(service_id, plan_id)
|
70
|
+
updated = patch("/apps/#{application_id}/addons/#{assignment_id}", body: { plan: plan_id }).body
|
71
|
+
to_nucleus_installed_service(updated)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @see Stub#remove_service
|
75
|
+
def remove_service(application_id, service_id)
|
76
|
+
# make sure service is bound to the application
|
77
|
+
assignment_id = raw_installed_service(application_id, service_id)[:id]
|
78
|
+
delete("/apps/#{application_id}/addons/#{assignment_id}")
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def service_plan_identifier(service_id, plan_id)
|
84
|
+
# process plan id_or_name to build the unique identifier
|
85
|
+
# a) is a UUID
|
86
|
+
return plan_id if Regexp::UUID_PATTERN =~ plan_id
|
87
|
+
# b) is valid identifier, contains ':'
|
88
|
+
return plan_id if /^[-\w]+:[-\w]+$/i =~ plan_id
|
89
|
+
# c) fetch id for name
|
90
|
+
return "#{service_id}:#{plan_id}" unless Regexp::UUID_PATTERN =~ service_id
|
91
|
+
# arriving here, service_id is UUID but plan_id is the name --> DOH!
|
92
|
+
# we return the plan_id and the request will presumably fail
|
93
|
+
plan_id
|
94
|
+
end
|
95
|
+
|
96
|
+
def raw_installed_service(application_id, service_id)
|
97
|
+
# here we probably receive the ID of the service, not the service assignment ID itself
|
98
|
+
installed = get("/apps/#{application_id}/addons/#{service_id}", expects: [200, 404])
|
99
|
+
if installed.status == 404
|
100
|
+
assignment_id = service_assignment_id(application_id, service_id)
|
101
|
+
raise Errors::AdapterResourceNotFoundError,
|
102
|
+
"Service #{service_id} is not assigned to application #{application_id}" unless assignment_id
|
103
|
+
return get("/apps/#{application_id}/addons/#{assignment_id}").body
|
104
|
+
end
|
105
|
+
installed.body
|
106
|
+
end
|
107
|
+
|
108
|
+
def service_assignment_id(application_id, service_id)
|
109
|
+
all_services = get("/apps/#{application_id}/addons").body
|
110
|
+
match = all_services.find do |addon|
|
111
|
+
addon[:addon_service][:id] == service_id || addon[:addon_service][:name] == service_id
|
112
|
+
end
|
113
|
+
return match[:id] if match
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_nucleus_service(service)
|
118
|
+
service[:description] = service.delete(:human_name)
|
119
|
+
service[:release] = service.delete(:state)
|
120
|
+
service[:required_services] = []
|
121
|
+
service[:free_plan] = free_plan?(service[:id])
|
122
|
+
service[:documentation_url] = "https://addons.heroku.com/#{service[:name]}"
|
123
|
+
service
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_nucleus_installed_service(installed_service)
|
127
|
+
service = service(installed_service[:addon_service][:id])
|
128
|
+
# get all variables and reject all that do not belong to the addon
|
129
|
+
unless installed_service[:config_vars].nil? && installed_service[:config_vars].empty?
|
130
|
+
vars = get("/apps/#{installed_service[:app][:id]}/config-vars").body
|
131
|
+
# ignore all vars that do not belong to the service
|
132
|
+
vars = vars.delete_if { |k| !installed_service[:config_vars].include?(k.to_s) }
|
133
|
+
# format to desired format
|
134
|
+
vars = vars.collect { |k, v| { key: k, value: v, description: nil } }
|
135
|
+
end
|
136
|
+
service[:properties] = vars ? vars : []
|
137
|
+
service[:active_plan] = installed_service[:plan][:id]
|
138
|
+
service[:web_url] = installed_service[:web_url]
|
139
|
+
service
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_nucleus_plan(plan)
|
143
|
+
# TODO: extract payment period to enum
|
144
|
+
plan[:costs] = [{ price: [amount: plan[:price][:cents] / 100.0, currency: 'USD'],
|
145
|
+
period: plan[:price][:unit], per_instance: false }]
|
146
|
+
plan[:free] = plan[:price][:cents] == 0
|
147
|
+
plan
|
148
|
+
end
|
149
|
+
|
150
|
+
def load_plans(service_id)
|
151
|
+
get("/addon-services/#{service_id}/plans").body
|
152
|
+
end
|
153
|
+
|
154
|
+
# Memoize this detection.
|
155
|
+
# The information is not critical, but takes some time to evaluate.
|
156
|
+
# Values are not expected to change often.
|
157
|
+
def free_plan?(service_id, plans = nil)
|
158
|
+
@free_plans ||= {}
|
159
|
+
return @free_plans[service_id] if @free_plans.key?(service_id)
|
160
|
+
plans = load_plans(service_id) unless plans
|
161
|
+
@free_plans[service_id] = plans.any? { |plan| plan[:price][:cents] == 0 }
|
162
|
+
@free_plans[service_id]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|