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,76 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# cloud control data management operations
|
6
|
-
module Data
|
7
|
-
# @see Stub#deploy
|
8
|
-
def deploy(application_id, file, compression_format)
|
9
|
-
# get deployment, also serves as 404 check for application
|
10
|
-
deployment = default_deployment(application_id)
|
11
|
-
current_state = application_state(deployment)
|
12
|
-
|
13
|
-
user = get('/user').body[0]
|
14
|
-
name = "nucleus.app.repo.cloudControl.deploy.#{application_id}.#{SecureRandom.uuid}"
|
15
|
-
# push to the deployment branch, here: nucleus
|
16
|
-
with_ssh_key do
|
17
|
-
deployer = GitDeployer.new(name, deployment[:branch], user[:email], NUCLEUS_DEPLOYMENT)
|
18
|
-
deployer.deploy(file, compression_format)
|
19
|
-
end
|
20
|
-
|
21
|
-
return if current_state == Enums::ApplicationStates::CREATED ||
|
22
|
-
current_state == Enums::ApplicationStates::DEPLOYED
|
23
|
-
|
24
|
-
# Deploy via the API, use version identifier -1 to refer a new build,
|
25
|
-
# but ONLY (!) if the application is not in the CREATED or DEPLOYED state
|
26
|
-
put("app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}", body: { version: '-1' })
|
27
|
-
end
|
28
|
-
|
29
|
-
# @see Stub#download
|
30
|
-
def download(application_id, compression_format)
|
31
|
-
# get deployment, also serves as 404 check for application
|
32
|
-
deployment = default_deployment(application_id)
|
33
|
-
if application_state(deployment) == Enums::ApplicationStates::CREATED
|
34
|
-
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
|
35
|
-
end
|
36
|
-
|
37
|
-
# compress files to archive but exclude the .git repo
|
38
|
-
name = "nucleus.app.repo.cloudControl.download.#{application_id}.#{SecureRandom.uuid}"
|
39
|
-
with_ssh_key do
|
40
|
-
GitDeployer.new(name, deployment[:branch], nil, NUCLEUS_DEPLOYMENT).download(compression_format, true)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# @see Stub#rebuild
|
45
|
-
def rebuild(application_id)
|
46
|
-
# get deployment, also serves as 404 check for application
|
47
|
-
deployment = default_deployment(application_id)
|
48
|
-
if application_state(deployment) == Enums::ApplicationStates::CREATED
|
49
|
-
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be rebuild'
|
50
|
-
end
|
51
|
-
|
52
|
-
user = get('/user').body[0]
|
53
|
-
name = "nucleus.app.repo.cloudControl.rebuild.#{application_id}.#{SecureRandom.uuid}"
|
54
|
-
|
55
|
-
with_ssh_key do
|
56
|
-
GitDeployer.new(name, deployment[:branch], user[:email], NUCLEUS_DEPLOYMENT).trigger_build
|
57
|
-
end
|
58
|
-
|
59
|
-
# now deploy via the API, use version identifier -1 to refer a new build
|
60
|
-
put("app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}", body: { version: '-1' })
|
61
|
-
|
62
|
-
# return with updated application
|
63
|
-
application(application_id)
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def register_key(user, type, key)
|
69
|
-
key_name = "nucleus-#{SecureRandom.uuid}"
|
70
|
-
post("/user/#{user}/key", body: { key: [type, key, key_name].join(' ') }).body[:key_id]
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# cloud control, CRUD operations for the application's domain object
|
6
|
-
module Domains
|
7
|
-
# cloud control URLs that are automatically assigned to applications as domain but can't be managed
|
8
|
-
CC_URLS = %w(cloudcontrolapp.com cloudcontrolled.com).freeze
|
9
|
-
|
10
|
-
# @see Stub#domains
|
11
|
-
def domains(application_id)
|
12
|
-
# no conversion needed, cc domains already have :name value
|
13
|
-
cc_domains = get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/alias").body
|
14
|
-
# the domain shall NOT be a CC system domain
|
15
|
-
cc_domains = cc_domains.find_all do |domain|
|
16
|
-
!CC_URLS.any? { |cc_domain| domain[:name].include? cc_domain }
|
17
|
-
end
|
18
|
-
# the list does not include the timestamps, fetch all
|
19
|
-
cc_domains.compact.collect { |domain| domain(application_id, domain[:name]) }
|
20
|
-
end
|
21
|
-
|
22
|
-
# @see Stub#domain
|
23
|
-
def domain(application_id, alias_name)
|
24
|
-
# no conversion needed, cc domains already have :name value
|
25
|
-
cc_domain = get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/alias/#{alias_name}").body
|
26
|
-
to_nucleus_domain(cc_domain)
|
27
|
-
end
|
28
|
-
|
29
|
-
# @see Stub#create_domain
|
30
|
-
def create_domain(application_id, domain)
|
31
|
-
# check if name is available
|
32
|
-
if domain?(application_id, domain[:name])
|
33
|
-
fail Errors::SemanticAdapterRequestError,
|
34
|
-
"Domain '#{domain[:name]}' is already assigned to the application"
|
35
|
-
end
|
36
|
-
|
37
|
-
# no conversion needed, cc domains already have :name value
|
38
|
-
cc_domain = post("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/alias",
|
39
|
-
body: { name: domain[:name] }).body
|
40
|
-
log.info("Please use this code to verify your custom application domain: #{cc_domain[:verification_code]}")
|
41
|
-
log.info('More information about the domain verification can be found at: '\
|
42
|
-
'https://www.cloudcontrol.com/dev-center/add-on-documentation/alias')
|
43
|
-
to_nucleus_domain(cc_domain)
|
44
|
-
end
|
45
|
-
|
46
|
-
# @see Stub#delete_domain
|
47
|
-
def delete_domain(application_id, alias_name)
|
48
|
-
delete("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/alias/#{alias_name}")
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def domain?(application_id, alias_name)
|
54
|
-
cc_domains = get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/alias").body
|
55
|
-
cc_domains.any? { |domain| domain[:name] == alias_name }
|
56
|
-
end
|
57
|
-
|
58
|
-
def to_nucleus_domain(domain)
|
59
|
-
domain[:id] = domain[:name]
|
60
|
-
domain[:created_at] = domain.delete(:date_created)
|
61
|
-
domain[:updated_at] = domain.delete(:date_modified)
|
62
|
-
domain
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
module Lifecycle
|
6
|
-
# @see Stub#start
|
7
|
-
def start(application_id)
|
8
|
-
deployment = default_deployment(application_id)
|
9
|
-
# fail if there is no deployment
|
10
|
-
unless data_uploaded?(deployment)
|
11
|
-
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be started'
|
12
|
-
end
|
13
|
-
|
14
|
-
# if no cloudControl deployment has been made, trigger it
|
15
|
-
if deployment[:version] == '-1'
|
16
|
-
# deploy via the API, use version identifier -1 to refer a new build
|
17
|
-
put("app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}", body: { version: '-1' })
|
18
|
-
end
|
19
|
-
|
20
|
-
# return the application object
|
21
|
-
to_nucleus_app(get("/app/#{application_id}").body, default_deployment(application_id))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# cloud control application's log management operations
|
6
|
-
module Logs
|
7
|
-
class LogPoller
|
8
|
-
# Initialize a new instance
|
9
|
-
# @param [BaseAdapter] adapter the adapter that needs the {LogPoller}
|
10
|
-
# @param [Hash] headers_to_use adapter headers, e.g. containing the authentication, that shall be used
|
11
|
-
# for requests when the headers can't be resolved line in deferred actions
|
12
|
-
def initialize(adapter, headers_to_use)
|
13
|
-
@adapter = adapter
|
14
|
-
@headers_to_use = headers_to_use
|
15
|
-
@last_log_entry = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
# Start the continuous polling of the logs.
|
19
|
-
# @param [String] application_name the name (the ID) of the application
|
20
|
-
# @param [Array<String>] logs_to_poll IDs of the logs to poll
|
21
|
-
# @param [StreamCallback] stream stream callback to push messages
|
22
|
-
# @return [void]
|
23
|
-
def start(application_name, logs_to_poll, stream)
|
24
|
-
@polling_active = true
|
25
|
-
# 1 log: wait 4 seconds between polls
|
26
|
-
# 4 logs: 1 seconds
|
27
|
-
timeout = logs_to_poll.length == 1 ? 4 : 1
|
28
|
-
logs_to_poll.each { |log_to_poll| @last_log_entry[log_to_poll] = nil }
|
29
|
-
|
30
|
-
fetch_action = lambda do
|
31
|
-
update_log(application_name, logs_to_poll, stream)
|
32
|
-
# start next iteration if we are still supposed to be active
|
33
|
-
EM.add_timer(timeout) { fetch_action.call } if @polling_active
|
34
|
-
end
|
35
|
-
# start the loop to poll the logs
|
36
|
-
EM.add_timer(timeout) { fetch_action.call }
|
37
|
-
end
|
38
|
-
|
39
|
-
# Stop the polling at the next shot
|
40
|
-
def stop
|
41
|
-
@polling_active = false
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def update_log(application_name, logs_to_poll, stream)
|
47
|
-
logs_to_poll.each do |log_to_poll|
|
48
|
-
# check again if we are still supposed to be active
|
49
|
-
break unless @polling_active
|
50
|
-
lines = @adapter.send(:cc_log_entries, application_name, log_to_poll,
|
51
|
-
@last_log_entry[log_to_poll], @headers_to_use)
|
52
|
-
next if lines.empty?
|
53
|
-
send_lines(lines, log_to_poll, stream)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def send_lines(lines, log_to_poll, stream)
|
58
|
-
# now sort by time
|
59
|
-
lines.sort_by! { |line| line[:time].to_f }
|
60
|
-
@last_log_entry[log_to_poll] = lines.last[:time] if lines
|
61
|
-
lines.each do |line|
|
62
|
-
line[:nucleus_origin] = log_to_poll
|
63
|
-
stream.send_message(@adapter.send(:format_log_entry, line[:nucleus_origin], line))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,103 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# cloud control application's log management operations
|
6
|
-
module Logs
|
7
|
-
# Cloud control log types. The +key+ and +id+ shall match the Nucleus definitions of log files,
|
8
|
-
# whereas the +name+ shall match the cloud control log id.
|
9
|
-
LOG_TYPES = {
|
10
|
-
all: { id: 'all', name: 'all', type: Enums::ApplicationLogfileType::OTHER },
|
11
|
-
request: { id: 'request', name: 'access', type: Enums::ApplicationLogfileType::REQUEST },
|
12
|
-
application: { id: 'application', name: 'error', type: Enums::ApplicationLogfileType::APPLICATION },
|
13
|
-
api: { id: 'api', name: 'deploy', type: Enums::ApplicationLogfileType::API },
|
14
|
-
system: { id: 'system', name: 'worker', type: Enums::ApplicationLogfileType::SYSTEM }
|
15
|
-
}.freeze
|
16
|
-
|
17
|
-
# @see Stub#logs
|
18
|
-
def logs(application_name)
|
19
|
-
# fails with 404 if application is not available and serves for timestamps
|
20
|
-
app = get("/app/#{application_name}").body
|
21
|
-
|
22
|
-
LOG_TYPES.values.collect do |log|
|
23
|
-
log[:created_at] = app[:date_created]
|
24
|
-
log[:updated_at] = app[:date_modified]
|
25
|
-
log
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# @see Stub#log?
|
30
|
-
def log?(application_name, log_id)
|
31
|
-
# fails with 404 if application is not available
|
32
|
-
get("/app/#{application_name}")
|
33
|
-
|
34
|
-
LOG_TYPES.key? log_id.to_sym
|
35
|
-
end
|
36
|
-
|
37
|
-
# cloud control shows the last 500 log messages if applicable
|
38
|
-
# @see Stub#tail
|
39
|
-
def tail(application_name, log_id, stream)
|
40
|
-
# cache headers as they are bound to a request and could be lost with the next tick
|
41
|
-
headers_to_use = headers
|
42
|
-
logs_to_poll = log_id == 'all' ? LOG_TYPES.keys - [:all] : [log_id]
|
43
|
-
poller = LogPoller.new(self, headers_to_use)
|
44
|
-
poller.start(application_name, logs_to_poll, stream)
|
45
|
-
TailStopper.new(poller, :stop)
|
46
|
-
end
|
47
|
-
|
48
|
-
# cloud control shows the last 500 log messages if applicable
|
49
|
-
# @see Stub#log_entries
|
50
|
-
def log_entries(application_name, log_id)
|
51
|
-
unless log?(application_name, log_id)
|
52
|
-
fail Errors::AdapterResourceNotFoundError,
|
53
|
-
"Invalid log file '#{log_id}', not available for application '#{application_name}'"
|
54
|
-
end
|
55
|
-
if log_id == 'all'
|
56
|
-
fetched_lines = []
|
57
|
-
(LOG_TYPES.keys - [:all]).each do |current_log_id|
|
58
|
-
cc_log_entries(application_name, current_log_id).each do |line|
|
59
|
-
line[:nucleus_origin] = current_log_id
|
60
|
-
fetched_lines.push(line)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
fetched_lines.sort_by! { |line| line[:time] }
|
64
|
-
fetched_lines.collect { |line| format_log_entry(line[:nucleus_origin], line) }
|
65
|
-
else
|
66
|
-
cc_log_entries(application_name, log_id).collect { |line| format_log_entry(log_id, line) }
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def cc_log_entries(app_name, log_id, time = nil, headers_to_use = nil)
|
73
|
-
log_name = LOG_TYPES[log_id.to_sym][:name]
|
74
|
-
# Hack, do not create fresh headers (which would fail) when in a deferred action
|
75
|
-
headers_to_use = headers unless headers_to_use
|
76
|
-
if time
|
77
|
-
get("/app/#{app_name}/deployment/#{NUCLEUS_DEPLOYMENT}/log/#{log_name}?timestamp=#{time}",
|
78
|
-
headers: headers_to_use).body
|
79
|
-
else
|
80
|
-
get("/app/#{app_name}/deployment/#{NUCLEUS_DEPLOYMENT}/log/#{log_name}", headers: headers_to_use).body
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def format_log_entry(log_id, line)
|
85
|
-
# format according to: https://github.com/cloudControl/cctrl/blob/master/cctrl/output.py
|
86
|
-
case log_id.to_sym
|
87
|
-
when :request
|
88
|
-
"#{line[:remote_host]} #{line[:remote_user]} #{line[:remote_logname]} [#{Time.at(line[:time]).iso8601}] "\
|
89
|
-
"#{line[:first_request_line]} #{line[:status]} #{line[:response_size_CLF]} #{line[:referer]} "\
|
90
|
-
"#{line[:user_agent]}"
|
91
|
-
when :system
|
92
|
-
"#{line[:time]} #{line[:wrk_id]} #{line[:message]}"
|
93
|
-
when :build
|
94
|
-
"#{Time.at(line[:time]).iso8601} [#{line[:hostname]}/#{line[:depl_id]}] #{line[:level]} #{line[:message]}"
|
95
|
-
when :error
|
96
|
-
"#{line[:time]} #{line[:type]} #{line[:message]}"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
module Regions
|
6
|
-
# @see Stub#regions
|
7
|
-
def regions
|
8
|
-
[default_region]
|
9
|
-
end
|
10
|
-
|
11
|
-
# @see Stub#region
|
12
|
-
def region(region_name)
|
13
|
-
fail Errors::AdapterResourceNotFoundError,
|
14
|
-
"Region '#{region_name}' does not exist at the endpoint" unless region_name.casecmp('default') == 0
|
15
|
-
default_region
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def default_region
|
21
|
-
{
|
22
|
-
id: 'default',
|
23
|
-
description: 'Default region, cloudControl does not support multi regions yet.',
|
24
|
-
created_at: Time.at(0).to_datetime,
|
25
|
-
updated_at: Time.at(0).to_datetime
|
26
|
-
}
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
module Scaling
|
6
|
-
# @see Stub#scale
|
7
|
-
def scale(application_id, instances)
|
8
|
-
# update the number of instances on the application's deployment
|
9
|
-
scale_response = put("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}",
|
10
|
-
body: { min_boxes: instances }).body
|
11
|
-
to_nucleus_app(get("/app/#{application_id}").body, scale_response)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# Semantic error messages that are specific for cloudControl
|
6
|
-
module SemanticErrors
|
7
|
-
# Get all cloudControl specific semantic error definitions.
|
8
|
-
# @return [Hash<Symbol,Hash<Symbol,String>>] the error message definitions, including the error +code+,
|
9
|
-
# e.g. +422_200_1+ and the +message+ that shall be formatted when used.
|
10
|
-
def semantic_error_messages
|
11
|
-
{
|
12
|
-
# Error code '300_1': Only one runtime is allowed per cloudControl application
|
13
|
-
only_one_runtime: { code: 422_300_1, message: 'cloudControl only allows 1 runtime per application' },
|
14
|
-
# Error code '300_2': Billing details required
|
15
|
-
billing_required: { code: 422_300_2,
|
16
|
-
message: 'cloudControl requires a billing account to allow this action: %s' },
|
17
|
-
# Error code '300_3': Malformed name, please follow the requirements of cloudControl app names
|
18
|
-
bad_name: { code: 422_300_3, message: '%s' },
|
19
|
-
# Error code '300_3': Malformed name, please follow the requirements of cloudControl app names
|
20
|
-
ambiguous_deployments: { code: 422_300_4, message: 'Unable to identify the deployment that shall be '\
|
21
|
-
'used. Nucleus require to find: a) exactly one deployment, b) a "default" deployment or '\
|
22
|
-
'c) a "nucleus" deployment' },
|
23
|
-
no_deployment: { code: 422_300_5, message: 'No deployment found. Nucleus requires to find: a) '\
|
24
|
-
'exactly one deployment, b) a "default" deployment or c) a "nucleus" deployment' }
|
25
|
-
}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,162 +0,0 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class CloudControl < Stub
|
5
|
-
# cloud control, operations for the application's addons
|
6
|
-
module Services
|
7
|
-
# @see Stub#services
|
8
|
-
def services
|
9
|
-
get('/addon').body.collect { |cc_service| to_nucleus_service(cc_service) }
|
10
|
-
end
|
11
|
-
|
12
|
-
# @see Stub#service
|
13
|
-
def service(service_name)
|
14
|
-
to_nucleus_service(get("/addon/#{service_name}").body)
|
15
|
-
end
|
16
|
-
|
17
|
-
# @see Stub#service_plans
|
18
|
-
def service_plans(service_name)
|
19
|
-
get("/addon/#{service_name}").body[:options].collect { |plan| to_nucleus_plan(plan) }
|
20
|
-
end
|
21
|
-
|
22
|
-
# @see Stub#service_plan
|
23
|
-
def service_plan(service_name, plan_name)
|
24
|
-
plan_name = plan_name?(plan_name) ? plan_name : "#{service_name}.#{plan_name}"
|
25
|
-
plan = get("/addon/#{service_name}").body[:options].find do |cc_plan|
|
26
|
-
cc_plan[:name] == plan_name
|
27
|
-
end
|
28
|
-
fail Errors::AdapterResourceNotFoundError,
|
29
|
-
"No such plan '#{plan_name}' for service '#{service_name}'" unless plan
|
30
|
-
to_nucleus_plan(plan)
|
31
|
-
end
|
32
|
-
|
33
|
-
# @see Stub#installed_services
|
34
|
-
def installed_services(application_id)
|
35
|
-
load_installed_addons(application_id).collect do |assignment|
|
36
|
-
# ignore config and alias addons, for us they are core parts of the application
|
37
|
-
next if %w(config.free alias.free).include?(assignment[:addon_option][:name])
|
38
|
-
service = service(parse_service_name(assignment[:addon_option][:name]))
|
39
|
-
to_nucleus_installed_service(service, assignment)
|
40
|
-
end.compact
|
41
|
-
end
|
42
|
-
|
43
|
-
# @see Stub#installed_service
|
44
|
-
def installed_service(application_id, service_name)
|
45
|
-
# we also require the installed plan to retrieve the service, the list does not include all properties :(
|
46
|
-
plan_name = active_plan(application_id, service_name)
|
47
|
-
assignment = get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/#{plan_name}").body
|
48
|
-
installed_service = service(parse_service_name(assignment[:addon_option][:name]))
|
49
|
-
to_nucleus_installed_service(installed_service, assignment)
|
50
|
-
end
|
51
|
-
|
52
|
-
# @see Stub#add_service
|
53
|
-
def add_service(application_id, service_entity, plan_entity)
|
54
|
-
plan_name = plan_name?(plan_entity[:id]) ? plan_entity[:id] : "#{service_entity[:id]}.#{plan_entity[:id]}"
|
55
|
-
created = post("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon",
|
56
|
-
body: { addon: plan_name }).body
|
57
|
-
to_nucleus_installed_service(service(service_entity[:id]), created)
|
58
|
-
end
|
59
|
-
|
60
|
-
# @see Stub#change_service
|
61
|
-
def change_service(application_id, service_id, plan_entity)
|
62
|
-
plan_name = active_plan(application_id, service_id)
|
63
|
-
fail Errors::SemanticAdapterRequestError,
|
64
|
-
"Plan '#{plan_entity[:id]}' is already active for service '#{service_id}' of application "\
|
65
|
-
"'#{application_id}'" if plan_name == plan_entity[:id]
|
66
|
-
|
67
|
-
updated = put("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/#{plan_name}",
|
68
|
-
body: { addon: plan_entity[:id] }).body
|
69
|
-
to_nucleus_installed_service(service(service_id), updated)
|
70
|
-
end
|
71
|
-
|
72
|
-
# @see Stub#remove_service
|
73
|
-
def remove_service(application_id, service_id)
|
74
|
-
plan_name = active_plan(application_id, service_id)
|
75
|
-
delete("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/#{plan_name}")
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def plan_name?(plan_name)
|
81
|
-
parts = plan_name.split('.')
|
82
|
-
# must have 2 parts and both must not be empty
|
83
|
-
parts.length == 2 && parts.all { |part| part.length > 0 }
|
84
|
-
end
|
85
|
-
|
86
|
-
def parse_service_name(plan_name)
|
87
|
-
parts = plan_name.split('.')
|
88
|
-
fail Errors::SemanticAdapterRequestError, 'Invalid service plan name: Name must contain only one dot, '\
|
89
|
-
"which separates the service and plan, e.g. 'mysql.free'" if parts.length != 2
|
90
|
-
parts[0]
|
91
|
-
end
|
92
|
-
|
93
|
-
def load_installed_addons(application_id)
|
94
|
-
get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon").body
|
95
|
-
end
|
96
|
-
|
97
|
-
def active_plan(application_id, service_id)
|
98
|
-
all_installed = load_installed_addons(application_id)
|
99
|
-
installed_service = all_installed.find do |service|
|
100
|
-
service[:addon_option][:name].start_with?("#{service_id}.") || service[:addon_option][:name] == service_id
|
101
|
-
end
|
102
|
-
fail Errors::AdapterResourceNotFoundError,
|
103
|
-
"No such service '#{service_id}' for application '#{application_id}'" unless installed_service
|
104
|
-
installed_service[:addon_option][:name]
|
105
|
-
end
|
106
|
-
|
107
|
-
def free_plan?(service)
|
108
|
-
service[:options].any? { |plan| plan[:thirty_days_price].to_i == 0 }
|
109
|
-
end
|
110
|
-
|
111
|
-
# The currency that is used for the prices is not stored within the API.
|
112
|
-
# However, we can identify the currency based on a list of known providers.
|
113
|
-
# As fallback, we assume the currency is EURO.
|
114
|
-
def currency
|
115
|
-
return 'USD' if endpoint_url.to_s.include?('dotcloudapp.com')
|
116
|
-
return 'CHF' if endpoint_url.to_s.include?('app.exo.io')
|
117
|
-
# EUR used for cloudControl, CLOUD & HEAT and as fallback
|
118
|
-
'EUR'
|
119
|
-
end
|
120
|
-
|
121
|
-
def to_nucleus_plan(plan)
|
122
|
-
plan[:id] = plan[:name]
|
123
|
-
plan[:free] = plan[:thirty_days_price].to_i == 0
|
124
|
-
plan[:description] = nil
|
125
|
-
# TODO: extract payment period to enum
|
126
|
-
plan[:costs] = [{ period: 'month', per_instance: plan[:price_is_per_box],
|
127
|
-
price: [amount: plan[:thirty_days_price].to_f, currency: currency] }]
|
128
|
-
plan[:created_at] = nil
|
129
|
-
plan[:updated_at] = nil
|
130
|
-
plan
|
131
|
-
end
|
132
|
-
|
133
|
-
def to_nucleus_service(service)
|
134
|
-
service[:id] = service[:name]
|
135
|
-
service[:release] = service.delete(:stage)
|
136
|
-
# the API does not contain any information, but the homepage does
|
137
|
-
service[:documentation_url] = "https://www.cloudcontrol.com/add-ons/#{service[:name]}"
|
138
|
-
service[:description] = nil
|
139
|
-
service[:created_at] = nil
|
140
|
-
service[:updated_at] = nil
|
141
|
-
service[:required_services] = []
|
142
|
-
service[:free_plan] = free_plan?(service)
|
143
|
-
service
|
144
|
-
end
|
145
|
-
|
146
|
-
def to_nucleus_installed_service(service, installed_service)
|
147
|
-
# settings ? ? ?
|
148
|
-
service[:active_plan] = installed_service[:addon_option][:name]
|
149
|
-
if installed_service[:settings] && !installed_service[:settings].empty?
|
150
|
-
properties = installed_service[:settings].collect do |key, value|
|
151
|
-
{ key: key, value: value, description: nil }
|
152
|
-
end
|
153
|
-
end
|
154
|
-
service[:properties] = properties ? properties : []
|
155
|
-
service[:web_url] = nil
|
156
|
-
service
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|