orchestration 0.1.0 → 0.2.0
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/.gitignore +14 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +4 -2
- data/Makefile +5 -0
- data/README.md +82 -16
- data/Rakefile +5 -3
- data/TODO +2 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/config/locales/en.yml +16 -0
- data/lib/orchestration/docker_compose/database_service.rb +51 -0
- data/lib/orchestration/docker_compose/mongo_service.rb +27 -0
- data/lib/orchestration/docker_compose/rabbitmq_service.rb +23 -0
- data/lib/orchestration/docker_compose/services.rb +48 -0
- data/lib/orchestration/docker_compose.rb +11 -0
- data/lib/orchestration/environment.rb +47 -0
- data/lib/orchestration/errors.rb +7 -0
- data/lib/orchestration/file_helpers.rb +88 -0
- data/lib/orchestration/healthcheck_base.rb +21 -0
- data/lib/orchestration/install_generator.rb +95 -0
- data/lib/orchestration/railtie.rb +9 -0
- data/lib/orchestration/service_check.rb +81 -0
- data/lib/orchestration/services/database/adapters/mysql2.rb +27 -0
- data/lib/orchestration/services/database/adapters/postgresql.rb +27 -0
- data/lib/orchestration/services/database/adapters/sqlite3.rb +23 -0
- data/lib/orchestration/services/database/adapters.rb +14 -0
- data/lib/orchestration/services/database/configuration.rb +114 -0
- data/lib/orchestration/services/database/healthcheck.rb +30 -0
- data/lib/orchestration/services/database.rb +15 -0
- data/lib/orchestration/services/mongo/configuration.rb +55 -0
- data/lib/orchestration/services/mongo/healthcheck.rb +34 -0
- data/lib/orchestration/services/mongo.rb +12 -0
- data/lib/orchestration/services/rabbitmq/configuration.rb +36 -0
- data/lib/orchestration/services/rabbitmq/healthcheck.rb +37 -0
- data/lib/orchestration/services/rabbitmq.rb +12 -0
- data/lib/orchestration/services.rb +10 -0
- data/lib/orchestration/settings.rb +43 -0
- data/lib/orchestration/templates/Dockerfile.tt +11 -0
- data/lib/orchestration/templates/Makefile.tt +53 -0
- data/lib/orchestration/terminal.rb +44 -0
- data/lib/orchestration/version.rb +3 -1
- data/lib/orchestration.rb +25 -2
- data/lib/tasks/orchestration.rake +39 -0
- data/orchestration.gemspec +34 -17
- metadata +213 -7
- data/CODE_OF_CONDUCT.md +0 -74
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module Database
|
6
|
+
class Configuration
|
7
|
+
attr_reader :adapter, :settings
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
@adapter = nil
|
12
|
+
@settings = nil
|
13
|
+
return unless defined?(ActiveRecord)
|
14
|
+
return unless File.exist?(@env.database_configuration_path)
|
15
|
+
|
16
|
+
setup
|
17
|
+
end
|
18
|
+
|
19
|
+
def friendly_config
|
20
|
+
adapter = @settings['adapter']
|
21
|
+
host = @settings['host']
|
22
|
+
port = @settings['port']
|
23
|
+
return "[#{adapter}]" if adapter == 'sqlite3'
|
24
|
+
return "[#{adapter}] #{host}" unless port.present?
|
25
|
+
|
26
|
+
"[#{adapter}] #{host}:#{port}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def setup
|
32
|
+
environments = parse(File.read(@env.database_configuration_path))
|
33
|
+
base = base_config(environments)
|
34
|
+
@adapter = adapter_object(base['adapter'])
|
35
|
+
@settings = base.merge(@adapter.credentials)
|
36
|
+
@settings.merge!(default_port) unless @settings.key?('port')
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(content)
|
40
|
+
yaml(erb(content))
|
41
|
+
end
|
42
|
+
|
43
|
+
def erb(content)
|
44
|
+
ERB.new(content).result
|
45
|
+
end
|
46
|
+
|
47
|
+
def yaml(content)
|
48
|
+
YAML.safe_load(content, [], [], true) # true: Allow aliases
|
49
|
+
end
|
50
|
+
|
51
|
+
def adapter_object(name)
|
52
|
+
{
|
53
|
+
'mysql2' => adapters::Mysql2,
|
54
|
+
'postgresql' => adapters::Postgresql,
|
55
|
+
'sqlite3' => adapters::Sqlite3
|
56
|
+
}.fetch(name).new
|
57
|
+
end
|
58
|
+
|
59
|
+
def base_config(environments)
|
60
|
+
missing_default unless environments.key?('default')
|
61
|
+
|
62
|
+
host = url_config['host'] || environments[@env.environment]['host']
|
63
|
+
|
64
|
+
environments
|
65
|
+
.fetch('default')
|
66
|
+
.merge(url_config)
|
67
|
+
.merge('host' => host)
|
68
|
+
end
|
69
|
+
|
70
|
+
def adapters
|
71
|
+
Orchestration::Services::Database::Adapters
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_port
|
75
|
+
return {} if @adapter.is_a?(adapters::Sqlite3)
|
76
|
+
|
77
|
+
{ 'port' => @adapter.default_port }
|
78
|
+
end
|
79
|
+
|
80
|
+
def url_config
|
81
|
+
return {} if @env.database_url.nil?
|
82
|
+
|
83
|
+
uri = URI.parse(@env.database_url)
|
84
|
+
|
85
|
+
{
|
86
|
+
'host' => uri.hostname,
|
87
|
+
'adapter' => adapter_name_from_scheme(uri.scheme),
|
88
|
+
'port' => uri.port
|
89
|
+
}.merge(query_params(uri))
|
90
|
+
end
|
91
|
+
|
92
|
+
def adapter_name_from_scheme(scheme)
|
93
|
+
return 'mysql2' if scheme == 'mysql'
|
94
|
+
return 'postgresql' if scheme == 'postgres'
|
95
|
+
return 'sqlite3' if scheme == 'sqlite3'
|
96
|
+
|
97
|
+
raise ArgumentError,
|
98
|
+
I18n.t('orchestration.unknown_scheme', scheme: scheme)
|
99
|
+
end
|
100
|
+
|
101
|
+
def query_params(uri)
|
102
|
+
return {} if uri.query.nil?
|
103
|
+
|
104
|
+
Hash[URI.decode_www_form(uri.query)]
|
105
|
+
end
|
106
|
+
|
107
|
+
def missing_default
|
108
|
+
raise DatabaseConfigurationError,
|
109
|
+
I18n.t('orchestration.database.missing_default')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module Database
|
6
|
+
class Healthcheck
|
7
|
+
include HealthcheckBase
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@configuration = Configuration.new(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
ActiveRecord::Base.establish_connection(@configuration.settings)
|
15
|
+
ActiveRecord::Base.connection
|
16
|
+
end
|
17
|
+
|
18
|
+
def connection_errors
|
19
|
+
[ActiveRecord::ConnectionNotEstablished].concat(adapter_errors)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def adapter_errors
|
25
|
+
@configuration.adapter.errors
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module Database
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'erb'
|
11
|
+
require 'uri'
|
12
|
+
|
13
|
+
require 'orchestration/services/database/adapters'
|
14
|
+
require 'orchestration/services/database/configuration'
|
15
|
+
require 'orchestration/services/database/healthcheck'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module Mongo
|
6
|
+
class Configuration
|
7
|
+
attr_reader :settings
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
@settings = nil
|
12
|
+
return unless defined?(Mongoid)
|
13
|
+
return unless File.exist?(@env.mongoid_configuration_path)
|
14
|
+
|
15
|
+
@settings = config.fetch(@env.environment)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ports
|
19
|
+
hosts_and_ports.map { |_host, port| port }
|
20
|
+
end
|
21
|
+
|
22
|
+
def friendly_config
|
23
|
+
hosts_string = hosts_and_ports.map do |host, port|
|
24
|
+
"#{host}:#{port}"
|
25
|
+
end.join(', ')
|
26
|
+
|
27
|
+
"[mongoid] #{hosts_string}"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def config
|
33
|
+
YAML.safe_load(
|
34
|
+
File.read(@env.mongoid_configuration_path), [], [], true
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def clients
|
39
|
+
# 'sessions' and 'clients' are synonymous but vary between versions of
|
40
|
+
# Mongoid: https://github.com/mongoid/mongoid/commit/657650bc4befa001c0f66e8788e9df6a1af37e84
|
41
|
+
key = @settings.key?('sessions') ? 'sessions' : 'clients'
|
42
|
+
|
43
|
+
@settings.fetch(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hosts_and_ports
|
47
|
+
clients.fetch('default').fetch('hosts').map do |host_string|
|
48
|
+
host, _, port = host_string.partition(':')
|
49
|
+
[host, (port.empty? ? PORT : port)]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module Mongo
|
6
|
+
class Healthcheck
|
7
|
+
include HealthcheckBase
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@configuration = Configuration.new(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connection_errors
|
14
|
+
[::Mongo::Error::NoServerAvailable]
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
# REVIEW: For some reason this is extremely slow. Worth trying
|
19
|
+
# to see if there's a faster way to fail.
|
20
|
+
Mongoid.load_configuration(@configuration.settings)
|
21
|
+
!Mongoid.default_client.database_names.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def clients
|
27
|
+
return Mongoid.sessions if Mongoid.respond_to?(:sessions)
|
28
|
+
|
29
|
+
Mongoid.clients
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module RabbitMQ
|
6
|
+
class Configuration
|
7
|
+
attr_reader :settings
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
@settings = nil
|
12
|
+
return unless defined?(RabbitMQ)
|
13
|
+
return unless File.exist?(@env.rabbitmq_configuration_path)
|
14
|
+
|
15
|
+
@settings = config.fetch(@env.environment)
|
16
|
+
@settings.merge!('port' => PORT) unless @settings.key?('port')
|
17
|
+
end
|
18
|
+
|
19
|
+
def friendly_config
|
20
|
+
host = @settings.fetch('host')
|
21
|
+
port = @settings.fetch('port')
|
22
|
+
|
23
|
+
"[bunny] amqp://#{host}:#{port}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def config
|
29
|
+
YAML.safe_load(
|
30
|
+
File.read(@env.rabbitmq_configuration_path), [], [], true
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
module Services
|
5
|
+
module RabbitMQ
|
6
|
+
class Healthcheck
|
7
|
+
include HealthcheckBase
|
8
|
+
|
9
|
+
def initialize(env)
|
10
|
+
@configuration = Configuration.new(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connection_errors
|
14
|
+
[
|
15
|
+
Bunny::TCPConnectionFailedForAllHosts,
|
16
|
+
AMQ::Protocol::EmptyResponseError
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect
|
21
|
+
host = @configuration.settings.fetch('host')
|
22
|
+
port = @configuration.settings.fetch('port')
|
23
|
+
|
24
|
+
connection = Bunny.new("amqp://#{host}:#{port}", log_file: devnull)
|
25
|
+
connection.start
|
26
|
+
connection.stop
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def devnull
|
32
|
+
File.open(File::NULL, 'w')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orchestration
|
4
|
+
class Settings
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
@dirty = false
|
8
|
+
@exist = File.exist?(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(identifier)
|
12
|
+
identifier.to_s.split('.').reduce(config) do |result, key|
|
13
|
+
(result || {}).fetch(key)
|
14
|
+
end
|
15
|
+
rescue KeyError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(identifier, val)
|
20
|
+
*keys, setting_key = identifier.to_s.split('.')
|
21
|
+
new_config = config || {}
|
22
|
+
parent = keys.reduce(new_config) { |result, key| result[key] ||= {} }
|
23
|
+
parent[setting_key] = val
|
24
|
+
@dirty ||= config != new_config
|
25
|
+
File.write(@path, new_config.to_yaml)
|
26
|
+
end
|
27
|
+
|
28
|
+
def dirty?
|
29
|
+
@dirty
|
30
|
+
end
|
31
|
+
|
32
|
+
def exist?
|
33
|
+
@exist
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def config
|
39
|
+
File.write(@path, {}.to_yaml) unless File.exist?(@path)
|
40
|
+
YAML.safe_load(File.read(@path))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
FROM ruby:<%= ruby_version %>
|
2
|
+
ARG BUNDLE_BITBUCKET__ORG
|
3
|
+
RUN apt-get update \
|
4
|
+
&& apt-get install -y node.js \
|
5
|
+
&& gem install bundler \
|
6
|
+
&& mkdir /application
|
7
|
+
WORKDIR /application
|
8
|
+
COPY Gemfile Gemfile.lock ./
|
9
|
+
RUN bundle install --deployment --without development test
|
10
|
+
ADD .build/context.tar.gz .
|
11
|
+
CMD ["bundle", "exec", "rails", "server", "-p", "3000"]
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
# Do not edit this file below this point. Any changes will be overwritten.
|
3
|
+
#
|
4
|
+
# Example `test` command which will start and wait for all services before
|
5
|
+
# running tests:
|
6
|
+
#
|
7
|
+
# test: start wait
|
8
|
+
# bundle exec rspec
|
9
|
+
# yarn test app/javascript
|
10
|
+
# bundle exec rubocop
|
11
|
+
# yarn run eslint app/javascript
|
12
|
+
#
|
13
|
+
.PHONY: docker docker-build docker-push wait-database start
|
14
|
+
|
15
|
+
### Container management commands ###
|
16
|
+
|
17
|
+
start:
|
18
|
+
docker-compose up -d
|
19
|
+
|
20
|
+
stop:
|
21
|
+
docker-compose down
|
22
|
+
|
23
|
+
### Service healthcheck commands ###
|
24
|
+
|
25
|
+
wait: <%= wait_commands %>
|
26
|
+
@echo "All Containers ready."
|
27
|
+
|
28
|
+
wait-database:
|
29
|
+
@RAILS_ENV=test bundle exec rake orchestration:db:wait
|
30
|
+
|
31
|
+
wait-mongo:
|
32
|
+
@RAILS_ENV=test bundle exec rake orchestration:mongo:wait
|
33
|
+
|
34
|
+
wait-rabbitmq:
|
35
|
+
@RAILS_ENV=test bundle exec rake orchestration:rabbitmq:wait
|
36
|
+
|
37
|
+
### Docker build commands ###
|
38
|
+
|
39
|
+
docker: docker-build docker-push
|
40
|
+
|
41
|
+
docker-build:
|
42
|
+
mkdir -p ./docker/.build
|
43
|
+
git show master:./Gemfile > ./docker/Gemfile
|
44
|
+
git show master:./Gemfile.lock > ./docker/Gemfile.lock
|
45
|
+
git archive --format tar.gz -o docker/.build/context.tar.gz master
|
46
|
+
docker build --build-arg BUNDLE_GITHUB__COM \
|
47
|
+
--build-arg BUNDLE_BITBUCKET__ORG \
|
48
|
+
-t $(shell bundle exec rake orchestration:docker:username)/<%= app_id %>:$(shell git rev-parse --short --verify master) \
|
49
|
+
./docker/
|
50
|
+
|
51
|
+
docker-push: VERSION := $(shell git rev-parse --short --verify master)
|
52
|
+
docker-push:
|
53
|
+
docker push $(shell bundle exec rake orchestration:docker:username)/<%= app_id %>:${VERSION}
|