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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +9 -0
  4. data/README.md +43 -72
  5. data/lib/nucleus/adapter_resolver.rb +3 -3
  6. data/lib/nucleus/adapters/base_adapter.rb +109 -109
  7. data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -111
  8. data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -141
  9. data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -97
  10. data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +5 -5
  11. data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -41
  12. data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +6 -6
  13. data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -33
  14. data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +6 -6
  15. data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -80
  16. data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -57
  17. data/lib/nucleus/adapters/v1/heroku/data.rb +78 -78
  18. data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -146
  19. data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -51
  20. data/lib/nucleus/adapters/v1/heroku/logs.rb +2 -2
  21. data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -42
  22. data/lib/nucleus/adapters/v1/heroku/services.rb +168 -168
  23. data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -65
  24. data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -68
  25. data/lib/nucleus/adapters/v1/openshift_v2/application.rb +1 -1
  26. data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -96
  27. data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -60
  28. data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -106
  29. data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -125
  30. data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -58
  31. data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -173
  32. data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -49
  33. data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -464
  34. data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -44
  35. data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -53
  36. data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +3 -3
  37. data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -95
  38. data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -36
  39. data/lib/nucleus/core/adapter_extensions/http_client.rb +5 -5
  40. data/lib/nucleus/core/common/files/archive_extractor.rb +1 -1
  41. data/lib/nucleus/core/common/files/archiver.rb +2 -2
  42. data/lib/nucleus/core/file_handling/file_manager.rb +64 -64
  43. data/lib/nucleus/core/file_handling/git_deployer.rb +133 -133
  44. data/lib/nucleus/core/import/adapter_configuration.rb +53 -53
  45. data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -26
  46. data/lib/nucleus/version.rb +1 -1
  47. data/nucleus.gemspec +2 -2
  48. data/spec/integration/api/auth_spec.rb +3 -3
  49. data/spec/spec_helper.rb +98 -98
  50. data/spec/test_suites.rake +1 -1
  51. data/spec/unit/adapters/git_deployer_spec.rb +262 -262
  52. data/spec/unit/common/helpers/auth_helper_spec.rb +1 -1
  53. data/tasks/evaluation.rake +1 -1
  54. data/wiki/adapter_tests.md +0 -7
  55. data/wiki/implement_new_adapter.md +1 -1
  56. metadata +4 -20
  57. data/config/adapters/cloud_control.yml +0 -32
  58. data/lib/nucleus/adapters/v1/cloud_control/application.rb +0 -108
  59. data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +0 -27
  60. data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +0 -23
  61. data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +0 -153
  62. data/lib/nucleus/adapters/v1/cloud_control/data.rb +0 -76
  63. data/lib/nucleus/adapters/v1/cloud_control/domains.rb +0 -68
  64. data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +0 -27
  65. data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +0 -71
  66. data/lib/nucleus/adapters/v1/cloud_control/logs.rb +0 -103
  67. data/lib/nucleus/adapters/v1/cloud_control/regions.rb +0 -32
  68. data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +0 -17
  69. data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +0 -31
  70. data/lib/nucleus/adapters/v1/cloud_control/services.rb +0 -162
  71. data/lib/nucleus/adapters/v1/cloud_control/token.rb +0 -17
  72. 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