orchestration 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}
|