orchestration 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/TODO +2 -12
  4. data/config/locales/en.yml +10 -13
  5. data/lib/orchestration/docker_compose/{application_service.rb → app_service.rb} +6 -5
  6. data/lib/orchestration/docker_compose/configuration.rb +69 -0
  7. data/lib/orchestration/docker_compose/database_service.rb +18 -25
  8. data/lib/orchestration/docker_compose/install_generator.rb +113 -0
  9. data/lib/orchestration/docker_compose/mongo_service.rb +15 -5
  10. data/lib/orchestration/docker_compose/nginx_proxy_service.rb +3 -2
  11. data/lib/orchestration/docker_compose/rabbitmq_service.rb +9 -5
  12. data/lib/orchestration/docker_compose.rb +3 -2
  13. data/lib/orchestration/environment.rb +32 -11
  14. data/lib/orchestration/errors.rb +3 -1
  15. data/lib/orchestration/file_helpers.rb +10 -5
  16. data/lib/orchestration/install_generator.rb +38 -51
  17. data/lib/orchestration/services/{application → app}/configuration.rb +5 -5
  18. data/lib/orchestration/services/{application → app}/healthcheck.rb +4 -4
  19. data/lib/orchestration/services/app.rb +13 -0
  20. data/lib/orchestration/services/configuration_base.rb +3 -1
  21. data/lib/orchestration/services/database/adapters/mysql2.rb +19 -0
  22. data/lib/orchestration/services/database/adapters/postgresql.rb +20 -0
  23. data/lib/orchestration/services/database/adapters/sqlite3.rb +4 -0
  24. data/lib/orchestration/services/database/configuration.rb +23 -13
  25. data/lib/orchestration/services/healthcheck_base.rb +1 -1
  26. data/lib/orchestration/services/nginx_proxy/configuration.rb +7 -2
  27. data/lib/orchestration/services.rb +1 -1
  28. data/lib/orchestration/templates/Makefile.erb +99 -40
  29. data/lib/orchestration/templates/application.mk.erb +14 -0
  30. data/lib/orchestration/templates/deploy.mk.erb +34 -0
  31. data/lib/orchestration/templates/docker-compose.override.yml.erb +1 -0
  32. data/lib/orchestration/templates/env.erb +6 -0
  33. data/lib/orchestration/templates/unicorn.rb.erb +1 -1
  34. data/lib/orchestration/terminal.rb +17 -1
  35. data/lib/orchestration/version.rb +1 -1
  36. data/lib/orchestration.rb +2 -1
  37. data/lib/tasks/orchestration.rake +7 -17
  38. metadata +13 -8
  39. data/lib/orchestration/docker_compose/services.rb +0 -59
  40. data/lib/orchestration/services/application.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e49d1834028ab1854b554f998dcd5779e5af3474
4
- data.tar.gz: 35185f308b9bc16794a906f9b7e4bfec8b790355
2
+ SHA256:
3
+ metadata.gz: 416fd08805e2ce12b2397a91f5baf894130f26d1ddf047f11a6b788b89783f16
4
+ data.tar.gz: 7b2f05787d2173d2e160a4ec40a4359e12f9e0d108384c7bd13c6fba43118692
5
5
  SHA512:
6
- metadata.gz: 9f1b206a4458192418ef09dcac765fc1fc3c85b94b0f724f6009eadd20732f8e08cd9415395ce349740857a1a9f87a236f585f6dda77e03ba1838fe52bb200bd
7
- data.tar.gz: e76e69bdf49ec909149510d8664776c1ff0c91e92a7f26eabeff70969bf987fc9910896127d9eac6ae7bca94c48fcee9fca6eed1e8a68150ecb261b98e6a1030
6
+ metadata.gz: 7a48ebb75ada7116256385d8bcf4d7db9a3bc6a9236b56b641078148a79b4fbb60cf75d4a488d0141415857d85f1d222d99adfd1212dd6346f7a7f6f14c7dbbc
7
+ data.tar.gz: f9df0d2800ea58090177d05b146236c44aff5e7cbbdd407b10f99214bea3fb8ae5de69f3893fd52eafa996491132cba9a25ecd7119c24320a4d420400e469396
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.3.3'
21
+ gem 'orchestration', '~> 0.3.4'
22
22
  ```
23
23
 
24
24
  And then build your bundle:
data/TODO CHANGED
@@ -1,5 +1,3 @@
1
- Provide volumes for databases and mount appropriate directories for adapter
2
-
3
1
  Refactor docker-compose services - these really belong in
4
2
  lib/orchestration/services/<service-name>/docker_compose.rb
5
3
 
@@ -7,13 +5,5 @@ Standardise on log formats - by policy or recommendation ?
7
5
 
8
6
  Redis support
9
7
 
10
- Stop supporting development mode. Provide a docker-compose.yml for loading
11
- development and test dependencies and expect user to run Rails application
12
- locally.
13
-
14
- Build a docker-compose.production.yml for running application in
15
- production (i.e. including application as a service and removing local port
16
- bindings).
17
-
18
- Make docker-compose file additive - detect existing services and add new ones
19
- rather than overwriting.
8
+ Use `Paint` instead of `Colorize` for colouring output - better licensing and no
9
+ monkeypatching `String` etc.
@@ -3,20 +3,21 @@ en:
3
3
  attempt_limit: "Unable to reconnect after %{limit} attempts. Aborting."
4
4
  default: "default"
5
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}"
6
+ app:
7
+ waiting: "Waiting for app: %{config}"
8
+ ready: "App is ready."
9
+ connection_error: "Error attempting to connect to app: received status code %{code}"
10
10
 
11
11
  database:
12
12
  waiting: "Waiting for database: %{config}"
13
13
  ready: "Database is ready."
14
+ unknown_environment: "Environment not defined in database configuration: %{environment}"
14
15
 
15
16
  mongo:
16
17
  waiting: "Waiting for Mongo: %{config}"
17
18
  ready: "Mongo is ready."
18
19
 
19
- nginx-proxy:
20
+ nginx_proxy:
20
21
  waiting: "Waiting for Nginx proxy: %{config}"
21
22
  ready: "Nginx proxy is ready."
22
23
 
@@ -39,11 +40,8 @@ en:
39
40
  prompt: "project name"
40
41
 
41
42
  rake:
42
- docker:
43
- username: "Output configured Docker username"
44
-
45
- application:
46
- wait: "Wait for database to become available"
43
+ app:
44
+ wait: "Wait for app to become available"
47
45
 
48
46
  database:
49
47
  wait: "Wait for database to become available"
@@ -54,11 +52,10 @@ en:
54
52
  mongo:
55
53
  wait: "Wait for Mongo to become available"
56
54
 
57
- nginx-proxy:
55
+ nginx_proxy:
58
56
  wait: "Wait for Nginx proxy to become available"
59
57
 
60
58
  rabbitmq:
61
59
  wait: "Wait for RabbitMQ to become available"
62
60
 
63
- rabbitmq:
64
- wait: "Initialise boilerplate for adding Docker to your application"
61
+ install: "Install Orchestration tools"
@@ -2,8 +2,9 @@
2
2
 
3
3
  module Orchestration
4
4
  module DockerCompose
5
- class ApplicationService
6
- def initialize(config)
5
+ class AppService
6
+ def initialize(config, environment)
7
+ @environment = environment
7
8
  @config = config
8
9
  end
9
10
 
@@ -27,8 +28,7 @@ module Orchestration
27
28
  'UNICORN_PRELOAD_APP' => '1',
28
29
  'UNICORN_TIMEOUT' => '60',
29
30
  'UNICORN_WORKER_PROCESSES' => '8',
30
- 'VIRTUAL_PORT' => '8080',
31
- 'VIRTUAL_HOST' => 'localhost'
31
+ 'VIRTUAL_PORT' => '8080'
32
32
  }.merge(inherited_environment)
33
33
  end
34
34
 
@@ -36,7 +36,8 @@ module Orchestration
36
36
  {
37
37
  'HOST_UID' => nil,
38
38
  'RAILS_ENV' => nil,
39
- 'SECRET_KEY_BASE' => nil
39
+ 'SECRET_KEY_BASE' => nil,
40
+ 'VIRTUAL_HOST' => nil
40
41
  }
41
42
  end
42
43
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module DockerCompose
5
+ class Configuration
6
+ def initialize(env, environment, selected_services)
7
+ @env = env # Global environment
8
+ @environment = environment # Current build environment
9
+ @selected_services = selected_services
10
+ end
11
+
12
+ def version
13
+ @env.docker_api_version
14
+ end
15
+
16
+ def services
17
+ Hash[services_enabled]
18
+ end
19
+
20
+ def volumes
21
+ public_volume.merge(database_volume).merge(mongo_volume)
22
+ end
23
+
24
+ private
25
+
26
+ def services_available
27
+ {
28
+ app: AppService,
29
+ database: DatabaseService,
30
+ mongo: MongoService,
31
+ rabbitmq: RabbitMQService,
32
+ nginx_proxy: NginxProxyService
33
+ }
34
+ end
35
+
36
+ def services_enabled
37
+ @selected_services.map do |service, config|
38
+ definition = service_definition(service, config)
39
+ next if definition.nil?
40
+
41
+ [service.to_s, definition]
42
+ end.compact
43
+ end
44
+
45
+ def public_volume
46
+ { @env.public_volume => {} }
47
+ end
48
+
49
+ def database_volume
50
+ return {} unless services.key?('database')
51
+
52
+ { @env.database_volume => {} }
53
+ end
54
+
55
+ def mongo_volume
56
+ return {} unless services.key?('mongo')
57
+
58
+ { @env.mongo_volume => {} }
59
+ end
60
+
61
+ def service_definition(service, config)
62
+ services_available
63
+ .fetch(service)
64
+ .new(config, @environment)
65
+ .definition
66
+ end
67
+ end
68
+ end
69
+ end
@@ -7,44 +7,37 @@ module Orchestration
7
7
  # container to simplify port mapping.
8
8
  PORT = 3354
9
9
 
10
- def initialize(config)
10
+ def initialize(config, environment)
11
+ @environment = environment
11
12
  @config = config
12
13
  end
13
14
 
14
15
  def definition
15
16
  return nil if @config.settings.nil?
17
+ return nil if @config.adapter.name == 'sqlite3'
16
18
 
17
- adapter = @config.settings.fetch('adapter')
18
- return nil if adapter == 'sqlite3'
19
-
20
- port = @config.settings.fetch('port')
21
19
  {
22
- 'image' => image_from_adapter(adapter),
23
- 'environment' => environment_from_adapter(adapter),
24
- 'ports' => ["#{port}:#{PORT}"]
25
- }
20
+ 'image' => @config.adapter.image,
21
+ 'environment' => @config.adapter.environment
22
+ }.merge(ports).merge(volumes)
26
23
  end
27
24
 
28
25
  private
29
26
 
30
- def image_from_adapter(adapter)
31
- {
32
- 'postgresql' => 'library/postgres',
33
- 'mysql2' => 'library/mysql'
34
- }.fetch(adapter)
27
+ def volume
28
+ @config.env.database_volume
35
29
  end
36
30
 
37
- def environment_from_adapter(adapter)
38
- {
39
- 'postgresql' => {
40
- 'PGPORT' => PORT.to_s,
41
- 'POSTGRES_PASSWORD' => 'password'
42
- },
43
- 'mysql2' => {
44
- 'MYSQL_ROOT_PASSWORD' => 'password',
45
- 'MYSQL_TCP_PORT' => PORT.to_s
46
- }
47
- }.fetch(adapter)
31
+ def ports
32
+ return {} unless %i[development test].include?(@environment)
33
+
34
+ { 'ports' => ["#{@config.settings.fetch('port')}:#{PORT}"] }
35
+ end
36
+
37
+ def volumes
38
+ return {} if @environment == :test
39
+
40
+ { 'volumes' => ["#{volume}:#{@config.adapter.data_dir}"] }
48
41
  end
49
42
  end
50
43
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module DockerCompose
5
+ class InstallGenerator
6
+ include FileHelpers
7
+
8
+ def initialize(env, terminal)
9
+ @env = env
10
+ @terminal = terminal
11
+ end
12
+
13
+ def docker_compose_yml
14
+ create_compose_file
15
+ end
16
+
17
+ def docker_compose_test_yml
18
+ create_compose_file(:test)
19
+ end
20
+
21
+ def docker_compose_development_yml
22
+ create_compose_file(:development)
23
+ end
24
+
25
+ def docker_compose_production_yml
26
+ create_compose_file(:production)
27
+ end
28
+
29
+ def docker_compose_override_yml
30
+ simple_copy(
31
+ 'docker-compose.override.yml',
32
+ @env.docker_compose_path(:override),
33
+ overwrite: false
34
+ )
35
+ end
36
+
37
+ def enabled_services(environment)
38
+ service_names(environment).select { |name| service_enabled?(name) }
39
+ end
40
+
41
+ private
42
+
43
+ def create_compose_file(environment = nil)
44
+ path = @env.docker_compose_path(environment)
45
+ create_file(
46
+ path,
47
+ structure(environment).to_yaml,
48
+ overwrite: false
49
+ )
50
+ end
51
+
52
+ def structure(environment = nil)
53
+ {
54
+ 'version' => compose_config(environment).version,
55
+ 'services' => services(environment)
56
+ }.merge('volumes' => volumes(environment))
57
+ end
58
+
59
+ def services(environment)
60
+ compose_config(environment).services
61
+ end
62
+
63
+ def volumes(environment)
64
+ return {} if environment.nil? || environment == :test
65
+
66
+ compose_config(environment).volumes
67
+ end
68
+
69
+ def compose_config(environment)
70
+ DockerCompose::Configuration.new(
71
+ @env,
72
+ environment,
73
+ Hash[configurations(environment)]
74
+ )
75
+ end
76
+
77
+ def service_names(environment)
78
+ case environment
79
+ when :test, :development
80
+ %i[database mongo rabbitmq]
81
+ when :production
82
+ %i[nginx_proxy app database mongo rabbitmq]
83
+ when nil
84
+ []
85
+ else
86
+ raise ArgumentError, environment.inspect
87
+ end
88
+ end
89
+
90
+ def configurations(environment)
91
+ service_names(environment).map do |key|
92
+ [key, configuration(key)]
93
+ end
94
+ end
95
+
96
+ def configuration(service)
97
+ {
98
+ app: Orchestration::Services::App::Configuration,
99
+ database: Orchestration::Services::Database::Configuration,
100
+ mongo: Orchestration::Services::Mongo::Configuration,
101
+ rabbitmq: Orchestration::Services::RabbitMQ::Configuration,
102
+ nginx_proxy: Orchestration::Services::NginxProxy::Configuration
103
+ }.fetch(service).new(@env)
104
+ end
105
+
106
+ def service_enabled?(service_name)
107
+ return false if configuration(service_name).settings.nil?
108
+
109
+ true
110
+ end
111
+ end
112
+ end
113
+ end
@@ -5,21 +5,31 @@ module Orchestration
5
5
  class MongoService
6
6
  PORT = 27_020
7
7
 
8
- def initialize(config)
8
+ def initialize(config, environment)
9
9
  @config = config
10
+ @environment = environment
10
11
  end
11
12
 
12
13
  def definition
13
14
  return nil if @config.settings.nil?
14
15
 
15
- {
16
- 'image' => 'library/mongo',
17
- 'ports' => ["#{local_port}:#{remote_port}"]
18
- }
16
+ { 'image' => 'library/mongo' }.merge(ports).merge(volumes)
19
17
  end
20
18
 
21
19
  private
22
20
 
21
+ def ports
22
+ return {} unless %i[development test].include?(@environment)
23
+
24
+ { 'ports' => ["#{local_port}:#{remote_port}"] }
25
+ end
26
+
27
+ def volumes
28
+ return {} if @environment == :test
29
+
30
+ { 'volumes' => ["#{@config.env.mongo_volume}:/data/db"] }
31
+ end
32
+
23
33
  def local_port
24
34
  _host, _, port = clients.fetch('default')
25
35
  .fetch('hosts')
@@ -3,14 +3,15 @@
3
3
  module Orchestration
4
4
  module DockerCompose
5
5
  class NginxProxyService
6
- def initialize(config)
6
+ def initialize(config, environment)
7
+ @environment = environment
7
8
  @config = config
8
9
  end
9
10
 
10
11
  def definition
11
12
  {
12
13
  'image' => 'rubyorchestration/nginx-proxy',
13
- 'ports' => %w[3000:80],
14
+ 'ports' => %w[${LISTEN_PORT}:80],
14
15
  'volumes' => [
15
16
  '/var/run/docker.sock:/tmp/docker.sock:ro',
16
17
  "#{@config.env.public_volume}:/var/www/public/:ro"
@@ -3,20 +3,24 @@
3
3
  module Orchestration
4
4
  module DockerCompose
5
5
  class RabbitMQService
6
- def initialize(config)
6
+ def initialize(config, environment)
7
7
  @config = config
8
+ @environment = environment
8
9
  end
9
10
 
10
11
  def definition
11
12
  return nil if @config.settings.nil?
12
13
 
14
+ { 'image' => 'library/rabbitmq' }.merge(ports)
15
+ end
16
+
17
+ def ports
18
+ return {} unless %i[development test].include?(@environment)
19
+
13
20
  host_port = @config.settings.fetch('port', 5672)
14
21
  container_port = Orchestration::Services::RabbitMQ::PORT
15
22
 
16
- {
17
- 'image' => 'library/rabbitmq',
18
- 'ports' => ["#{host_port}:#{container_port}"]
19
- }
23
+ { 'ports' => ["#{host_port}:#{container_port}"] }
20
24
  end
21
25
  end
22
26
  end
@@ -5,9 +5,10 @@ module Orchestration
5
5
  end
6
6
  end
7
7
 
8
- require 'orchestration/docker_compose/services'
8
+ require 'orchestration/docker_compose/install_generator'
9
+ require 'orchestration/docker_compose/configuration'
9
10
 
10
- require 'orchestration/docker_compose/application_service'
11
+ require 'orchestration/docker_compose/app_service'
11
12
  require 'orchestration/docker_compose/database_service'
12
13
  require 'orchestration/docker_compose/mongo_service'
13
14
  require 'orchestration/docker_compose/nginx_proxy_service'
@@ -32,23 +32,36 @@ module Orchestration
32
32
  root.join('.orchestration.yml')
33
33
  end
34
34
 
35
- def docker_compose_configuration_path
36
- orchestration_root.join('docker-compose.yml')
35
+ def docker_api_version
36
+ '3.7'
37
37
  end
38
38
 
39
- def docker_compose_config
40
- YAML.safe_load(File.read(docker_compose_configuration_path))
39
+ def docker_compose_path(env = nil)
40
+ return orchestration_root.join('docker-compose.yml') if env.nil?
41
+
42
+ orchestration_root.join("docker-compose.#{env}.yml")
43
+ end
44
+
45
+ def docker_compose_config(env = nil)
46
+ env ||= environment
47
+ YAML.safe_load(File.read(docker_compose_path(env)))
41
48
  end
42
49
 
43
- def docker_compose_config?
44
- docker_compose_configuration_path.file?
50
+ def docker_compose_config?(env = nil)
51
+ env ||= environment
52
+ docker_compose_path(env).file?
45
53
  end
46
54
 
47
- def default_application_name
55
+ def default_app_name
56
+ default = root.basename.to_s
57
+ return default unless defined?(Rails)
58
+ # Edge case if Rails is used as a dependency but we are not a Rails app:
59
+ return default if Rails.application.class.parent == Object
60
+
48
61
  Rails.application.class.parent.name.underscore
49
62
  end
50
63
 
51
- def application_name
64
+ def app_name
52
65
  settings.get('docker.repository')
53
66
  end
54
67
 
@@ -57,8 +70,8 @@ module Orchestration
57
70
  end
58
71
 
59
72
  def root
60
- return Rails.root if defined?(Rails) && Rails.root
61
-
73
+ defined?(Rails) && Rails.root ? Rails.root : Pathname.new(Dir.pwd)
74
+ rescue NoMethodError
62
75
  Pathname.new(Dir.pwd)
63
76
  end
64
77
 
@@ -71,7 +84,15 @@ module Orchestration
71
84
  end
72
85
 
73
86
  def public_volume
74
- "#{application_name}_public"
87
+ "#{app_name}_public"
88
+ end
89
+
90
+ def database_volume
91
+ "#{app_name}_database"
92
+ end
93
+
94
+ def mongo_volume
95
+ "#{app_name}_mongo"
75
96
  end
76
97
  end
77
98
  end
@@ -3,7 +3,9 @@
3
3
  module Orchestration
4
4
  class OrchestrationError < StandardError; end
5
5
 
6
- class ApplicationConnectionError < OrchestrationError; end
6
+ class AppConnectionError < OrchestrationError; end
7
7
  class DatabaseConfigurationError < OrchestrationError; end
8
8
  class MongoConfigurationError < OrchestrationError; end
9
+
10
+ class UnknownEnvironmentError < DatabaseConfigurationError; end
9
11
  end
@@ -27,11 +27,15 @@ module Orchestration
27
27
  end
28
28
 
29
29
  def relative_path(path)
30
- path.relative_path_from(Rails.root).to_s
30
+ path.relative_path_from(@env.root).to_s
31
31
  end
32
32
 
33
- def simple_copy(template_name, dest)
34
- update_file(dest, template(template_name))
33
+ def simple_copy(template_name, dest, options = {})
34
+ update_file(
35
+ dest,
36
+ template(template_name, env: @env),
37
+ overwrite: options.fetch(:overwrite, true)
38
+ )
35
39
  end
36
40
 
37
41
  def create_file(path, content, options = {})
@@ -44,12 +48,13 @@ module Orchestration
44
48
  @terminal.write(:create, relative_path(path))
45
49
  end
46
50
 
47
- def update_file(path, content)
51
+ def update_file(path, content, options = {})
48
52
  present = File.exist?(path)
49
53
  return create_file(path, content) unless present
50
54
 
55
+ overwrite = options.fetch(:overwrite, true)
51
56
  previous_content = File.read(path) if present
52
- if present && previous_content == content
57
+ if present && (!overwrite || previous_content == content)
53
58
  return @terminal.write(:skip, relative_path(path))
54
59
  end
55
60