orchestration 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +1 -1
  4. data/TODO +11 -5
  5. data/config/locales/en.yml +13 -1
  6. data/lib/Rakefile +5 -0
  7. data/lib/orchestration/docker_compose/application_service.rb +20 -22
  8. data/lib/orchestration/docker_compose/mongo_service.rb +17 -8
  9. data/lib/orchestration/docker_compose/nginx_proxy_service.rb +19 -0
  10. data/lib/orchestration/docker_compose/services.rb +4 -2
  11. data/lib/orchestration/docker_compose.rb +1 -0
  12. data/lib/orchestration/environment.rb +11 -1
  13. data/lib/orchestration/errors.rb +2 -0
  14. data/lib/orchestration/file_helpers.rb +3 -3
  15. data/lib/orchestration/install_generator.rb +29 -8
  16. data/lib/orchestration/service_check.rb +1 -1
  17. data/lib/orchestration/services/application/configuration.rb +29 -2
  18. data/lib/orchestration/services/application/healthcheck.rb +37 -0
  19. data/lib/orchestration/services/application.rb +3 -0
  20. data/lib/orchestration/services/configuration_base.rb +44 -0
  21. data/lib/orchestration/services/database/configuration.rb +19 -21
  22. data/lib/orchestration/services/database/healthcheck.rb +2 -0
  23. data/lib/orchestration/services/healthcheck_base.rb +38 -0
  24. data/lib/orchestration/services/mongo/configuration.rb +4 -25
  25. data/lib/orchestration/services/mongo/healthcheck.rb +2 -8
  26. data/lib/orchestration/services/nginx_proxy/configuration.rb +21 -0
  27. data/lib/orchestration/services/nginx_proxy/healthcheck.rb +23 -0
  28. data/lib/orchestration/services/nginx_proxy.rb +11 -0
  29. data/lib/orchestration/services/rabbitmq/configuration.rb +4 -5
  30. data/lib/orchestration/services/rabbitmq/healthcheck.rb +4 -3
  31. data/lib/orchestration/services.rb +4 -0
  32. data/lib/orchestration/templates/{Dockerfile.tt → Dockerfile.erb} +5 -2
  33. data/lib/orchestration/templates/{Makefile.tt → Makefile.erb} +30 -14
  34. data/lib/orchestration/templates/entrypoint.sh.erb +6 -0
  35. data/lib/orchestration/templates/unicorn.rb.erb +17 -0
  36. data/lib/orchestration/version.rb +1 -1
  37. data/lib/orchestration.rb +4 -1
  38. data/lib/tasks/orchestration.rake +15 -1
  39. data/orchestration.gemspec +3 -0
  40. metadata +56 -5
  41. data/lib/orchestration/healthcheck_base.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f8ceff8fc2dd990496a6de51dddca57caa2e7000
4
- data.tar.gz: dd0507265efb67c67ea5164cb2a7ff8b22675f0d
3
+ metadata.gz: 1ff223f91a5979874db88e7e55a788b9c7a645f5
4
+ data.tar.gz: d910c778ab54470515efdfa2f514853a91f5f0ea
5
5
  SHA512:
6
- metadata.gz: 6f254e30b288a9d20aba6b55abd7fc01fd8973690bb31f430904f870a40088a6b0b7b4c5f7677c81c1cf02c6ed99c2fc8552a3a2ead211ed3bf877757dccea80
7
- data.tar.gz: fc397b5f3341a7f6b2eda9e3231159c5fa5460a9feb3b5059472c5ea21de94796a3f55a4b7ad18571ef6a99eeb1ba3c3896efa7868024ccc577679777ae621fb
6
+ metadata.gz: e03236d51bfd3476b89cbe27a3811f2e4455c1c50b741901bd98a9db539cd029df39d8bdd0547228279b532252a1a484e7716f37c0f86a82c162847ff48a293a
7
+ data.tar.gz: 1538b05bdf17e1ecb6478992aff82ededd06cbfcf23b87dec959f2a8afc66cc408f5171de36539d9ba148b0b66372e57126c7f7023c0b135fbbefff881b6853d
data/.gitignore CHANGED
@@ -21,5 +21,6 @@ spec/dummy/.gitignore
21
21
  spec/dummy/Makefile
22
22
  spec/dummy/docker/*
23
23
  spec/dummy/docker-compose.yml
24
+ spec/dummy/config/unicorn.rb
24
25
 
25
26
  .byebug_history
data/README.md CHANGED
@@ -18,7 +18,7 @@ Containers are automatically created for the following dependencies:
18
18
  Add this line to your application's Gemfile:
19
19
 
20
20
  ```ruby
21
- gem 'orchestration', '~> 0.2.2'
21
+ gem 'orchestration', '~> 0.2.3'
22
22
  ```
23
23
 
24
24
  And then build your bundle:
data/TODO CHANGED
@@ -1,9 +1,15 @@
1
1
  Be aware of yarn and include build steps in Dockerfile if present. Use dashboard
2
2
  front end as a reference.
3
3
 
4
- Create a production docker-compose.yml and set:
5
- - RAILS_ENV
6
- - SECRET_KEY_BASE
7
- - DATABASE_URL (from config/database.yml)
8
-
9
4
  Provide volumes for databases and mount appropriate directories for adapter
5
+
6
+ Refactor docker-compose services - these really belong in
7
+ lib/orchestration/services/<service-name>/docker_compose.rb
8
+
9
+ Add a note to README to handle `RAILS_LOG_TO_STDOUT` in development -
10
+ unfortunately Rails ignores this value in development mode. (It's configured in
11
+ config/environments/production.rb)
12
+
13
+ Standardise on log formats - by policy or recommendation ?
14
+
15
+ Redis support
@@ -2,15 +2,27 @@ en:
2
2
  orchestration:
3
3
  unknown_scheme: "Unrecognised database adapter for URL scheme: %{scheme}"
4
4
  attempt_limit: "Unable to reconnect after %{limit} attempts. Aborting."
5
+
6
+ application:
7
+ waiting: "Waiting for application: %{config}"
8
+ ready: "Application is ready."
9
+ connection_error: "Error attempting to connect to application: received status code %{code}"
10
+
5
11
  database:
6
12
  waiting: "Waiting for database: %{config}"
7
13
  ready: "Database is ready."
8
- missing_default: "database.yml must define a `default` section."
14
+
9
15
  mongo:
10
16
  waiting: "Waiting for Mongo: %{config}"
11
17
  ready: "Mongo is ready."
18
+
19
+ nginxproxy:
20
+ waiting: "Waiting for Nginx proxy: %{config}"
21
+ ready: "Nginx proxy is ready."
22
+
12
23
  rabbitmq:
13
24
  waiting: "Waiting for RabbitMQ: %{config}"
14
25
  ready: "RabbitMQ is ready."
26
+
15
27
  docker:
16
28
  username_request: "Enter your Docker registry username (used for tagging images)"
data/lib/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'orchestration'
4
+
5
+ import Orchestration.root.join('lib', 'tasks', 'orchestration.rake')
@@ -3,48 +3,46 @@
3
3
  module Orchestration
4
4
  module DockerCompose
5
5
  class ApplicationService
6
- PORT = 3000
7
-
8
6
  def initialize(config)
9
7
  @config = config
10
- @env = config.environment
11
8
  end
12
9
 
13
10
  def definition
14
11
  {
15
12
  'image' => image,
13
+ 'entrypoint' => '/entrypoint.sh',
14
+ 'command' => %w[
15
+ bundle exec unicorn -c /application/config/unicorn.rb
16
+ ],
16
17
  'environment' => environment,
17
- 'ports' => ["#{PORT}:#{PORT}"]
18
+ 'expose' => [8080]
18
19
  }
19
20
  end
20
21
 
21
22
  private
22
23
 
23
24
  def image
24
- "#{@env.settings.get('docker.username')}/#{@env.application_name}"
25
+ "#{@config.docker_username}/#{@config.application_name}"
25
26
  end
26
27
 
27
28
  def environment
28
29
  {
29
- # `nil` values will inherit from environment or `.env` file.
30
- 'RAILS_ENV' => nil,
31
- 'SECRET_KEY_BASE' => nil,
32
- 'DATABASE_URL' => database_url,
33
- 'RAILS_LOG_TO_STDOUT' => '1'
34
- }
30
+ 'DATABASE_URL' => @config.database_url,
31
+ 'RAILS_LOG_TO_STDOUT' => '1',
32
+ 'UNICORN_PRELOAD_APP' => '1',
33
+ 'UNICORN_TIMEOUT' => '60',
34
+ 'UNICORN_WORKER_PROCESSES' => '8',
35
+ 'VIRTUAL_PORT' => '8080',
36
+ 'VIRTUAL_HOST' => 'localhost'
37
+ }.merge(inherited_environment)
35
38
  end
36
39
 
37
- def database_url
38
- settings = @config.database_settings
39
- return nil if settings.fetch('adapter') == 'sqlite3'
40
-
41
- scheme = settings.fetch('scheme')
42
- database = settings.fetch('database')
43
- username = settings.fetch('username')
44
- password = settings.fetch('password')
45
- port = DatabaseService::PORT
46
-
47
- "#{scheme}://#{username}:#{password}@database:#{port}/#{database}"
40
+ def inherited_environment
41
+ {
42
+ 'HOST_UID' => nil,
43
+ 'RAILS_ENV' => nil,
44
+ 'SECRET_KEY_BASE' => nil
45
+ }
48
46
  end
49
47
  end
50
48
  end
@@ -10,18 +10,27 @@ module Orchestration
10
10
  def definition
11
11
  return nil if @config.settings.nil?
12
12
 
13
- # REVIEW: If the host application defines multiple mongo hosts then we
14
- # create one service instance and point them all at the same service.
15
- # Instead we should probably create a separate service for each.
16
- ports = @config.ports.map do |port|
17
- "#{port}:#{Orchestration::Services::Mongo::PORT}"
18
- end
19
-
20
13
  {
21
14
  'image' => 'library/mongo',
22
- 'ports' => ports
15
+ 'ports' => ["#{local_port}:#{remote_port}"]
23
16
  }
24
17
  end
18
+
19
+ private
20
+
21
+ def local_port
22
+ _host, _, port = @config.settings
23
+ .fetch('clients')
24
+ .fetch('default')
25
+ .fetch('hosts')
26
+ .first
27
+ .partition(':')
28
+ port.empty? ? remote_port : port
29
+ end
30
+
31
+ def remote_port
32
+ Orchestration::Services::Mongo::PORT
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module DockerCompose
5
+ class NginxProxyService
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def definition
11
+ {
12
+ 'image' => 'jwilder/nginx-proxy',
13
+ 'ports' => %w[3000:80],
14
+ 'volumes' => ['/var/run/docker.sock:/tmp/docker.sock:ro']
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -8,7 +8,8 @@ module Orchestration
8
8
  'application' => options.fetch(:application, nil),
9
9
  'database' => options.fetch(:database, nil),
10
10
  'mongo' => options.fetch(:mongo, nil),
11
- 'rabbitmq' => options.fetch(:rabbitmq, nil)
11
+ 'rabbitmq' => options.fetch(:rabbitmq, nil),
12
+ 'nginx-proxy' => options.fetch(:nginx_proxy, nil)
12
13
  }
13
14
  end
14
15
 
@@ -31,7 +32,8 @@ module Orchestration
31
32
  { name: 'application', class: ApplicationService },
32
33
  { name: 'database', class: DatabaseService },
33
34
  { name: 'mongo', class: MongoService },
34
- { name: 'rabbitmq', class: RabbitMQService }
35
+ { name: 'rabbitmq', class: RabbitMQService },
36
+ { name: 'nginx-proxy', class: NginxProxyService }
35
37
  ]
36
38
  end
37
39
 
@@ -10,4 +10,5 @@ require 'orchestration/docker_compose/services'
10
10
  require 'orchestration/docker_compose/application_service'
11
11
  require 'orchestration/docker_compose/database_service'
12
12
  require 'orchestration/docker_compose/mongo_service'
13
+ require 'orchestration/docker_compose/nginx_proxy_service'
13
14
  require 'orchestration/docker_compose/rabbitmq_service'
@@ -32,6 +32,14 @@ module Orchestration
32
32
  root.join('.orchestration.yml')
33
33
  end
34
34
 
35
+ def docker_compose_configuration_path
36
+ root.join('docker-compose.yml')
37
+ end
38
+
39
+ def docker_compose_config
40
+ YAML.safe_load(File.read(docker_compose_configuration_path))
41
+ end
42
+
35
43
  def application_name
36
44
  Rails.application.class.parent.name.underscore
37
45
  end
@@ -41,7 +49,9 @@ module Orchestration
41
49
  end
42
50
 
43
51
  def root
44
- Rails.root
52
+ return Rails.root if defined?(Rails) && Rails.root
53
+
54
+ Pathname.new(Dir.pwd)
45
55
  end
46
56
  end
47
57
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Orchestration
4
4
  class OrchestrationError < StandardError; end
5
+
6
+ class ApplicationConnectionError < OrchestrationError; end
5
7
  class DatabaseConfigurationError < OrchestrationError; end
6
8
  class MongoConfigurationError < OrchestrationError; end
7
9
  end
@@ -4,9 +4,9 @@ module Orchestration
4
4
  module FileHelpers
5
5
  private
6
6
 
7
- def template(template_name, environment = {})
7
+ def template(template_name, context = {})
8
8
  Erubis::Eruby.new(read_template(template_name))
9
- .result(environment)
9
+ .result(context)
10
10
  end
11
11
 
12
12
  def delete_and_inject_after(path, pattern, replacement)
@@ -82,7 +82,7 @@ module Orchestration
82
82
  end
83
83
 
84
84
  def read_template(template)
85
- File.read(templates_path.join("#{template}.tt"))
85
+ File.read(templates_path.join("#{template}.erb"))
86
86
  end
87
87
  end
88
88
  end
@@ -35,15 +35,19 @@ module Orchestration
35
35
  end
36
36
 
37
37
  def dockerfile
38
- docker_dir = Rails.root.join('docker')
39
- path = docker_dir.join('Dockerfile')
40
38
  content = template('Dockerfile', ruby_version: RUBY_VERSION)
41
- FileUtils.mkdir(docker_dir) unless Dir.exist?(docker_dir)
39
+ write_file(docker_dir.join('Dockerfile'), content, overwrite: false)
40
+ end
41
+
42
+ def entrypoint
43
+ content = template('entrypoint.sh')
44
+ path = docker_dir.join('entrypoint.sh')
42
45
  write_file(path, content, overwrite: false)
46
+ FileUtils.chmod('a+x', path)
43
47
  end
44
48
 
45
49
  def gitignore
46
- path = Rails.root.join('.gitignore')
50
+ path = @env.root.join('.gitignore')
47
51
  entries = [
48
52
  'docker/.build',
49
53
  'docker/Gemfile',
@@ -54,18 +58,25 @@ module Orchestration
54
58
  end
55
59
 
56
60
  def docker_compose
57
- path = Rails.root.join('docker-compose.yml')
61
+ path = @env.root.join('docker-compose.yml')
58
62
  return if File.exist?(path)
59
63
 
60
64
  docker_compose = DockerCompose::Services.new(
61
65
  application: configuration(:application),
62
66
  database: configuration(:database),
63
67
  mongo: configuration(:mongo),
64
- rabbitmq: configuration(:rabbitmq)
68
+ rabbitmq: configuration(:rabbitmq),
69
+ nginx_proxy: configuration(:nginx_proxy)
65
70
  )
66
71
  write_file(path, docker_compose.structure.to_yaml)
67
72
  end
68
73
 
74
+ def unicorn
75
+ content = template('unicorn.rb')
76
+ path = @env.root.join('config', 'unicorn.rb')
77
+ write_file(path, content, overwrite: false)
78
+ end
79
+
69
80
  private
70
81
 
71
82
  def configuration(service)
@@ -73,7 +84,8 @@ module Orchestration
73
84
  application: Services::Application::Configuration,
74
85
  database: Services::Database::Configuration,
75
86
  mongo: Services::Mongo::Configuration,
76
- rabbitmq: Services::RabbitMQ::Configuration
87
+ rabbitmq: Services::RabbitMQ::Configuration,
88
+ nginx_proxy: Services::NginxProxy::Configuration
77
89
  }.fetch(service).new(@env)
78
90
  end
79
91
 
@@ -81,7 +93,9 @@ module Orchestration
81
93
  [
82
94
  configuration(:database).settings.nil? ? nil : 'wait-database',
83
95
  configuration(:mongo).settings.nil? ? nil : 'wait-mongo',
84
- configuration(:rabbitmq).settings.nil? ? nil : 'wait-rabbitmq'
96
+ configuration(:rabbitmq).settings.nil? ? nil : 'wait-rabbitmq',
97
+ 'wait-nginx-proxy',
98
+ 'wait-application'
85
99
  ].compact.join(' ')
86
100
  end
87
101
 
@@ -91,5 +105,12 @@ module Orchestration
91
105
  @terminal.write(:setup, I18n.t('orchestration.docker.username_request'))
92
106
  settings.set('docker.username', @terminal.read('[username]:'))
93
107
  end
108
+
109
+ def docker_dir
110
+ path = @env.root.join('docker')
111
+ FileUtils.mkdir(path) unless Dir.exist?(path)
112
+
113
+ path
114
+ end
94
115
  end
95
116
  end
@@ -64,7 +64,7 @@ module Orchestration
64
64
  def echo_failure
65
65
  @terminal.write(
66
66
  :failure,
67
- I18n.t('orchestration.attempt_limit', limit: ATTEMPT_LIMIT)
67
+ I18n.t('orchestration.attempt_limit', limit: @attempt_limit)
68
68
  )
69
69
  end
70
70
 
@@ -4,17 +4,44 @@ module Orchestration
4
4
  module Services
5
5
  module Application
6
6
  class Configuration
7
+ include ConfigurationBase
8
+
9
+ self.service_name = 'application'
10
+
7
11
  def initialize(env)
8
12
  @env = env
13
+ @settings = {} # Included for interface consistency; currently unused.
14
+ end
15
+
16
+ def docker_username
17
+ @env.settings.get('docker.username')
18
+ end
19
+
20
+ def application_name
21
+ @env.application_name
9
22
  end
10
23
 
11
- def environment
12
- @env
24
+ def friendly_config
25
+ "[#{application_name}] #{host}:#{local_port}"
13
26
  end
14
27
 
15
28
  def database_settings
16
29
  Database::Configuration.new(@env).settings
17
30
  end
31
+
32
+ def database_url
33
+ settings = database_settings
34
+ return nil if settings.fetch('adapter') == 'sqlite3'
35
+
36
+ scheme = settings.fetch('scheme')
37
+ database = settings.fetch('database')
38
+ username = settings.fetch('username')
39
+ password = settings.fetch('password')
40
+ port = settings.fetch('port')
41
+ host = Database::Configuration.service_name
42
+
43
+ "#{scheme}://#{username}:#{password}@#{host}:#{port}/#{database}"
44
+ end
18
45
  end
19
46
  end
20
47
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module Application
6
+ class Healthcheck
7
+ include HealthcheckBase
8
+
9
+ def initialize(env)
10
+ @configuration = Configuration.new(env)
11
+ end
12
+
13
+ def connect
14
+ response = Net::HTTP.get_response(
15
+ URI("http://localhost:#{@configuration.local_port}")
16
+ )
17
+ connection_error(response.code) if connection_error?(response.code)
18
+ end
19
+
20
+ def connection_errors
21
+ [Errno::ECONNREFUSED, ApplicationConnectionError]
22
+ end
23
+
24
+ private
25
+
26
+ def connection_error(code)
27
+ raise ApplicationConnectionError,
28
+ I18n.t('orchestration.application.connection_error', code: code)
29
+ end
30
+
31
+ def connection_error?(code)
32
+ %w[502 503 500].include?(code)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -7,4 +7,7 @@ module Orchestration
7
7
  end
8
8
  end
9
9
 
10
+ require 'net/http'
11
+
10
12
  require 'orchestration/services/application/configuration'
13
+ require 'orchestration/services/application/healthcheck'
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module ConfigurationBase
6
+ attr_reader :settings
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def service_name=(val)
14
+ @service_name = val
15
+ end
16
+
17
+ def service_name
18
+ return @service_name unless @service_name.nil?
19
+
20
+ raise ArgumentError,
21
+ "Must call `self.name=` on #{self.class.service_name}"
22
+ end
23
+ end
24
+
25
+ def host
26
+ 'localhost'
27
+ end
28
+
29
+ def local_port
30
+ name = self.class.service_name
31
+ key = name == 'application' ? 'nginx-proxy' : name
32
+
33
+ @env.docker_compose_config
34
+ .fetch('services')
35
+ .fetch(key)
36
+ .fetch('ports')
37
+ .first
38
+ .partition(':')
39
+ .first
40
+ .to_i
41
+ end
42
+ end
43
+ end
44
+ end
@@ -4,7 +4,11 @@ module Orchestration
4
4
  module Services
5
5
  module Database
6
6
  class Configuration
7
- attr_reader :adapter, :settings
7
+ include ConfigurationBase
8
+
9
+ self.service_name = 'database'
10
+
11
+ attr_reader :adapter
8
12
 
9
13
  def initialize(env)
10
14
  @env = env
@@ -13,28 +17,24 @@ module Orchestration
13
17
  return unless defined?(ActiveRecord)
14
18
  return unless File.exist?(@env.database_configuration_path)
15
19
 
20
+ @environments = parse(File.read(@env.database_configuration_path))
16
21
  setup
17
22
  end
18
23
 
19
24
  def friendly_config
20
- adapter = @settings['adapter']
21
- host = @settings['host']
22
- port = @settings['port']
25
+ adapter = @settings.fetch('adapter')
23
26
  return "[#{adapter}]" if adapter == 'sqlite3'
24
- return "[#{adapter}] #{host}" unless port.present?
25
27
 
26
- "[#{adapter}] #{host}:#{port}"
28
+ "[#{adapter}] #{host}:#{local_port}"
27
29
  end
28
30
 
29
31
  private
30
32
 
31
33
  def setup
32
- environments = parse(File.read(@env.database_configuration_path))
33
- base = base_config(environments)
34
34
  @adapter = adapter_object(base['adapter'])
35
35
  @settings = base.merge(@adapter.credentials)
36
36
  .merge('scheme' => scheme_name(base['adapter']))
37
- @settings.merge!(default_port) unless @settings.key?('port')
37
+ @settings.merge!(default_port) if @settings['port'].nil?
38
38
  end
39
39
 
40
40
  def parse(content)
@@ -57,15 +57,18 @@ module Orchestration
57
57
  }.fetch(name).new
58
58
  end
59
59
 
60
- def base_config(environments)
61
- missing_default unless environments.key?('default')
60
+ def environment
61
+ @environments[@env.environment]
62
+ end
62
63
 
63
- host = url_config['host'] || environments[@env.environment]['host']
64
+ def base
65
+ environment.merge(url_config).merge('host' => host)
66
+ end
64
67
 
65
- environments
66
- .fetch('default')
67
- .merge(url_config)
68
- .merge('host' => host)
68
+ def host
69
+ return nil if @adapter.is_a?(adapters::Sqlite3)
70
+
71
+ super
69
72
  end
70
73
 
71
74
  def adapters
@@ -109,11 +112,6 @@ module Orchestration
109
112
  Hash[URI.decode_www_form(uri.query)]
110
113
  end
111
114
 
112
- def missing_default
113
- raise DatabaseConfigurationError,
114
- I18n.t('orchestration.database.missing_default')
115
- end
116
-
117
115
  def adapter_mapping
118
116
  {
119
117
  'mysql' => 'mysql2',
@@ -6,6 +6,8 @@ module Orchestration
6
6
  class Healthcheck
7
7
  include HealthcheckBase
8
8
 
9
+ dependencies 'active_record'
10
+
9
11
  def initialize(env)
10
12
  @configuration = Configuration.new(env)
11
13
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module HealthcheckBase
6
+ attr_reader :configuration
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def start(env = nil, terminal = nil, options = {})
14
+ load_dependencies
15
+ exit_on_error = options.fetch(:exit_on_error, true)
16
+ options.delete(:exit_on_error)
17
+ env ||= Environment.new
18
+ terminal ||= Terminal.new
19
+ check = ServiceCheck.new(new(env), terminal, options)
20
+
21
+ exit 1 if !check.run && exit_on_error
22
+ end
23
+
24
+ def dependencies(*args)
25
+ @dependencies = args
26
+ end
27
+
28
+ private
29
+
30
+ def load_dependencies
31
+ return if @dependencies.nil?
32
+
33
+ @dependencies.map { |dependency| require dependency }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -4,7 +4,9 @@ module Orchestration
4
4
  module Services
5
5
  module Mongo
6
6
  class Configuration
7
- attr_reader :settings
7
+ include ConfigurationBase
8
+
9
+ self.service_name = 'mongo'
8
10
 
9
11
  def initialize(env)
10
12
  @env = env
@@ -15,16 +17,8 @@ module Orchestration
15
17
  @settings = config.fetch(@env.environment)
16
18
  end
17
19
 
18
- def ports
19
- hosts_and_ports.map { |_host, port| port }
20
- end
21
-
22
20
  def friendly_config
23
- hosts_string = hosts_and_ports.map do |host, port|
24
- "#{host}:#{port}"
25
- end.join(', ')
26
-
27
- "[mongoid] #{hosts_string}"
21
+ "[mongoid] #{host}:#{local_port}"
28
22
  end
29
23
 
30
24
  private
@@ -34,21 +28,6 @@ module Orchestration
34
28
  File.read(@env.mongoid_configuration_path), [], [], true
35
29
  )
36
30
  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
31
  end
53
32
  end
54
33
  end
@@ -6,6 +6,8 @@ module Orchestration
6
6
  class Healthcheck
7
7
  include HealthcheckBase
8
8
 
9
+ dependencies 'mongoid'
10
+
9
11
  def initialize(env)
10
12
  @configuration = Configuration.new(env)
11
13
  end
@@ -20,14 +22,6 @@ module Orchestration
20
22
  Mongoid.load_configuration(@configuration.settings)
21
23
  !Mongoid.default_client.database_names.empty?
22
24
  end
23
-
24
- private
25
-
26
- def clients
27
- return Mongoid.sessions if Mongoid.respond_to?(:sessions)
28
-
29
- Mongoid.clients
30
- end
31
25
  end
32
26
  end
33
27
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module NginxProxy
6
+ class Configuration
7
+ include ConfigurationBase
8
+
9
+ self.service_name = 'nginx-proxy'
10
+
11
+ def initialize(env)
12
+ @env = env
13
+ end
14
+
15
+ def friendly_config
16
+ "[nginx-proxy] #{host}:#{local_port}"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module NginxProxy
6
+ class Healthcheck
7
+ include HealthcheckBase
8
+
9
+ def initialize(env)
10
+ @configuration = Configuration.new(env)
11
+ end
12
+
13
+ def connect
14
+ Net::HTTP.start('localhost', 3000)
15
+ end
16
+
17
+ def connection_errors
18
+ [Errno::ECONNREFUSED]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module NginxProxy
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'orchestration/services/nginx_proxy/configuration'
11
+ require 'orchestration/services/nginx_proxy/healthcheck'
@@ -4,7 +4,9 @@ module Orchestration
4
4
  module Services
5
5
  module RabbitMQ
6
6
  class Configuration
7
- attr_reader :settings
7
+ include ConfigurationBase
8
+
9
+ self.service_name = 'rabbitmq'
8
10
 
9
11
  def initialize(env)
10
12
  @env = env
@@ -17,10 +19,7 @@ module Orchestration
17
19
  end
18
20
 
19
21
  def friendly_config
20
- host = @settings.fetch('host')
21
- port = @settings.fetch('port')
22
-
23
- "[bunny] amqp://#{host}:#{port}"
22
+ "[bunny] amqp://#{host}:#{local_port}"
24
23
  end
25
24
 
26
25
  private
@@ -6,6 +6,8 @@ module Orchestration
6
6
  class Healthcheck
7
7
  include HealthcheckBase
8
8
 
9
+ dependencies 'bunny'
10
+
9
11
  def initialize(env)
10
12
  @configuration = Configuration.new(env)
11
13
  end
@@ -18,10 +20,9 @@ module Orchestration
18
20
  end
19
21
 
20
22
  def connect
21
- host = @configuration.settings.fetch('host')
22
- port = @configuration.settings.fetch('port')
23
+ port = @configuration.local_port
23
24
 
24
- connection = Bunny.new("amqp://#{host}:#{port}", log_file: devnull)
25
+ connection = Bunny.new("amqp://localhost:#{port}", log_file: devnull)
25
26
  connection.start
26
27
  connection.stop
27
28
  end
@@ -5,7 +5,11 @@ module Orchestration
5
5
  end
6
6
  end
7
7
 
8
+ require 'orchestration/services/configuration_base'
9
+ require 'orchestration/services/healthcheck_base'
10
+
8
11
  require 'orchestration/services/application'
9
12
  require 'orchestration/services/database'
10
13
  require 'orchestration/services/mongo'
14
+ require 'orchestration/services/nginx_proxy'
11
15
  require 'orchestration/services/rabbitmq'
@@ -1,11 +1,14 @@
1
1
  FROM ruby:<%= ruby_version %>
2
2
  ARG BUNDLE_BITBUCKET__ORG
3
+ ARG BUNDLE_GITHUB__COM
3
4
  RUN apt-get update \
4
- && apt-get install -y node.js \
5
+ && apt-get install -y node.js gosu \
6
+ && rm -rf /var/lib/apt/lists/* \
5
7
  && gem install bundler \
8
+ && gem install unicorn \
6
9
  && mkdir /application
7
10
  WORKDIR /application
8
11
  COPY Gemfile Gemfile.lock ./
9
12
  RUN bundle install --deployment
13
+ COPY entrypoint.sh /
10
14
  ADD .build/context.tar.gz .
11
- CMD ["bundle", "exec", "rails", "server", "-p", "3000", "-b", "0.0.0.0"]
@@ -10,21 +10,27 @@
10
10
  # bundle exec rubocop
11
11
  # yarn run eslint app/javascript
12
12
  #
13
- .PHONY: docker docker-build docker-push wait-database start
13
+ .PHONY: start stop migrate docker build push start <%= wait_commands %>
14
14
 
15
15
  ### Container management commands ###
16
16
 
17
+ COMPOSE:=HOST_UID=$(shell id -u) docker-compose
18
+
17
19
  start:
18
- docker-compose up -d
20
+ @echo "Starting containers..."
21
+ @${COMPOSE} up -d --scale application=$${INSTANCES:-1}
22
+ @make wait
19
23
 
20
24
  stop:
21
- docker-compose down
25
+ @echo "Stopping containers..."
26
+ @${COMPOSE} down
27
+ @echo "All containers stopped."
22
28
 
23
29
  ### Database utility commands ###
24
30
 
25
31
  migrate: wait-database
26
32
  @echo "Running migrations..."
27
- @docker-compose run application bundle exec rake db:migrate
33
+ @${COMPOSE} run application bundle exec rake db:migrate
28
34
  @echo "Migrations complete."
29
35
 
30
36
  ### Service healthcheck commands ###
@@ -32,30 +38,40 @@ migrate: wait-database
32
38
  wait: <%= wait_commands %>
33
39
  @echo "All Containers ready."
34
40
 
41
+ wait-application:
42
+ @bin/rake orchestration:application:wait
43
+
35
44
  wait-database:
36
- @bin/rake orchestration:db:wait
45
+ @bin/rake orchestration:database:wait
37
46
 
38
47
  wait-mongo:
39
48
  @bin/rake orchestration:mongo:wait
40
49
 
50
+ wait-nginx-proxy:
51
+ @bin/rake orchestration:nginx_proxy:wait
52
+
41
53
  wait-rabbitmq:
42
54
  @bin/rake orchestration:rabbitmq:wait
43
55
 
44
56
  ### Docker build commands ###
45
57
 
46
- docker: docker-build docker-push
58
+ docker: build push
47
59
 
48
- docker-build:
49
- mkdir -p ./docker/.build
50
- git show master:./Gemfile > ./docker/Gemfile
51
- git show master:./Gemfile.lock > ./docker/Gemfile.lock
52
- git archive --format tar.gz -o docker/.build/context.tar.gz master
53
- docker build --build-arg BUNDLE_GITHUB__COM \
60
+ build:
61
+ @echo "Preparing build."
62
+ @mkdir -p ./docker/.build
63
+ @git show master:./Gemfile > ./docker/Gemfile
64
+ @git show master:./Gemfile.lock > ./docker/Gemfile.lock
65
+ @echo "Building..."
66
+ @git archive --format tar.gz -o docker/.build/context.tar.gz master
67
+ @docker build \
68
+ --build-arg BUNDLE_GITHUB__COM \
54
69
  --build-arg BUNDLE_BITBUCKET__ORG \
55
70
  -t $(shell bin/rake orchestration:docker:username)/<%= app_id %> \
56
71
  -t $(shell bin/rake orchestration:docker:username)/<%= app_id %>:$(shell git rev-parse --short --verify master) \
57
72
  ./docker/
73
+ @echo "Build complete."
58
74
 
59
- docker-push: VERSION := $(shell git rev-parse --short --verify master)
60
- docker-push:
75
+ push: VERSION := $(shell git rev-parse --short --verify master)
76
+ push:
61
77
  docker push $(shell bin/rake orchestration:docker:username)/<%= app_id %>:${VERSION}
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ set -u
3
+ useradd -u ${HOST_UID} -m -o owner
4
+ mkdir -p /application/tmp/pids
5
+ chown -R owner:owner /application/tmp /application/log /application/db
6
+ exec gosu owner "$@"
@@ -0,0 +1,17 @@
1
+ listen '0.0.0.0:8080', :tcp_nopush => true
2
+
3
+ pid '/application/tmp/pids/unicorn.pid'
4
+
5
+ preload_app ENV.fetch('UNICORN_PRELOAD_APP', '1') == '1'
6
+
7
+ timeout ENV.fetch('UNICORN_TIMEOUT', 60).to_i
8
+
9
+ worker_processes ENV.fetch('UNICORN_WORKER_PROCESSES', 8).to_i
10
+
11
+ before_fork do |_server, _worker|
12
+ ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord)
13
+ end
14
+
15
+ after_fork do |_server, _worker|
16
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Orchestration
4
- VERSION = '0.2.2'
4
+ VERSION = '0.2.3'
5
5
  end
data/lib/orchestration.rb CHANGED
@@ -12,7 +12,6 @@ require 'orchestration/docker_compose'
12
12
  require 'orchestration/environment'
13
13
  require 'orchestration/errors'
14
14
  require 'orchestration/file_helpers'
15
- require 'orchestration/healthcheck_base'
16
15
  require 'orchestration/install_generator'
17
16
  require 'orchestration/railtie'
18
17
  require 'orchestration/service_check'
@@ -25,4 +24,8 @@ module Orchestration
25
24
  def self.root
26
25
  Pathname.new(File.dirname(__dir__))
27
26
  end
27
+
28
+ def self.rakefile
29
+ root.join('lib', 'Rakefile')
30
+ end
28
31
  end
@@ -8,7 +8,14 @@ namespace :orchestration do
8
8
  Orchestration::InstallGenerator.start
9
9
  end
10
10
 
11
- namespace :db do
11
+ namespace :application do
12
+ desc 'Wait for application to become available'
13
+ task :wait do
14
+ Orchestration::Services::Application::Healthcheck.start
15
+ end
16
+ end
17
+
18
+ namespace :database do
12
19
  desc 'Wait for database to become available'
13
20
  task :wait do
14
21
  Orchestration::Services::Database::Healthcheck.start
@@ -22,6 +29,13 @@ namespace :orchestration do
22
29
  end
23
30
  end
24
31
 
32
+ namespace :nginx_proxy do
33
+ desc 'Wait for Nginx proxy to become available'
34
+ task :wait do
35
+ Orchestration::Services::NginxProxy::Healthcheck.start
36
+ end
37
+ end
38
+
25
39
  namespace :rabbitmq do
26
40
  desc 'Wait for database to become available'
27
41
  task :wait do
@@ -27,12 +27,15 @@ Gem::Specification.new do |spec|
27
27
  spec.add_runtime_dependency 'colorize', '~> 0.8.1'
28
28
  spec.add_runtime_dependency 'erubis', '~> 2.7'
29
29
  spec.add_runtime_dependency 'i18n', '>= 0.5'
30
+ spec.add_runtime_dependency 'unicorn'
30
31
 
31
32
  spec.add_development_dependency 'activerecord', '~> 5.2'
32
33
  spec.add_development_dependency 'bundler', '~> 1.16'
33
34
  spec.add_development_dependency 'bunny', '~> 2.12'
34
35
  spec.add_development_dependency 'byebug', '~> 10.0'
35
36
  spec.add_development_dependency 'mongoid', '~> 7.0'
37
+ spec.add_development_dependency 'mysql2', '~> 0.5.2'
38
+ spec.add_development_dependency 'pg', '~> 1.1'
36
39
  spec.add_development_dependency 'rails', '~> 5.2'
37
40
  spec.add_development_dependency 'rake', '~> 10.0'
38
41
  spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orchestration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-30 00:00:00.000000000 Z
11
+ date: 2018-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: unicorn
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: activerecord
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,34 @@ dependencies:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
138
  version: '7.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mysql2
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.5.2
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.5.2
153
+ - !ruby/object:Gem::Dependency
154
+ name: pg
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.1'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.1'
125
167
  - !ruby/object:Gem::Dependency
126
168
  name: rails
127
169
  requirement: !ruby/object:Gem::Requirement
@@ -245,23 +287,26 @@ files:
245
287
  - bin/rubocop
246
288
  - bin/setup
247
289
  - config/locales/en.yml
290
+ - lib/Rakefile
248
291
  - lib/orchestration.rb
249
292
  - lib/orchestration/docker_compose.rb
250
293
  - lib/orchestration/docker_compose/application_service.rb
251
294
  - lib/orchestration/docker_compose/database_service.rb
252
295
  - lib/orchestration/docker_compose/mongo_service.rb
296
+ - lib/orchestration/docker_compose/nginx_proxy_service.rb
253
297
  - lib/orchestration/docker_compose/rabbitmq_service.rb
254
298
  - lib/orchestration/docker_compose/services.rb
255
299
  - lib/orchestration/environment.rb
256
300
  - lib/orchestration/errors.rb
257
301
  - lib/orchestration/file_helpers.rb
258
- - lib/orchestration/healthcheck_base.rb
259
302
  - lib/orchestration/install_generator.rb
260
303
  - lib/orchestration/railtie.rb
261
304
  - lib/orchestration/service_check.rb
262
305
  - lib/orchestration/services.rb
263
306
  - lib/orchestration/services/application.rb
264
307
  - lib/orchestration/services/application/configuration.rb
308
+ - lib/orchestration/services/application/healthcheck.rb
309
+ - lib/orchestration/services/configuration_base.rb
265
310
  - lib/orchestration/services/database.rb
266
311
  - lib/orchestration/services/database/adapters.rb
267
312
  - lib/orchestration/services/database/adapters/mysql2.rb
@@ -269,15 +314,21 @@ files:
269
314
  - lib/orchestration/services/database/adapters/sqlite3.rb
270
315
  - lib/orchestration/services/database/configuration.rb
271
316
  - lib/orchestration/services/database/healthcheck.rb
317
+ - lib/orchestration/services/healthcheck_base.rb
272
318
  - lib/orchestration/services/mongo.rb
273
319
  - lib/orchestration/services/mongo/configuration.rb
274
320
  - lib/orchestration/services/mongo/healthcheck.rb
321
+ - lib/orchestration/services/nginx_proxy.rb
322
+ - lib/orchestration/services/nginx_proxy/configuration.rb
323
+ - lib/orchestration/services/nginx_proxy/healthcheck.rb
275
324
  - lib/orchestration/services/rabbitmq.rb
276
325
  - lib/orchestration/services/rabbitmq/configuration.rb
277
326
  - lib/orchestration/services/rabbitmq/healthcheck.rb
278
327
  - lib/orchestration/settings.rb
279
- - lib/orchestration/templates/Dockerfile.tt
280
- - lib/orchestration/templates/Makefile.tt
328
+ - lib/orchestration/templates/Dockerfile.erb
329
+ - lib/orchestration/templates/Makefile.erb
330
+ - lib/orchestration/templates/entrypoint.sh.erb
331
+ - lib/orchestration/templates/unicorn.rb.erb
281
332
  - lib/orchestration/terminal.rb
282
333
  - lib/orchestration/version.rb
283
334
  - lib/tasks/orchestration.rake
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Orchestration
4
- module HealthcheckBase
5
- attr_reader :configuration
6
-
7
- def self.included(base)
8
- base.extend(ClassMethods)
9
- end
10
-
11
- module ClassMethods
12
- def start(env = nil, terminal = nil)
13
- env ||= Environment.new
14
- terminal ||= Terminal.new
15
- check = ServiceCheck.new(new(env), terminal)
16
-
17
- exit 1 unless check.run
18
- end
19
- end
20
- end
21
- end