orchestration 0.3.17 → 0.4.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/LICENSE +7 -0
  4. data/MANIFEST +76 -0
  5. data/Makefile +3 -3
  6. data/README.md +162 -137
  7. data/Rakefile +2 -2
  8. data/config/locales/en.yml +3 -1
  9. data/lib/orchestration/docker_compose/app_service.rb +84 -13
  10. data/lib/orchestration/docker_compose/compose_configuration.rb +69 -0
  11. data/lib/orchestration/docker_compose/database_service.rb +15 -13
  12. data/lib/orchestration/docker_compose/install_generator.rb +3 -2
  13. data/lib/orchestration/docker_compose/mongo_service.rb +5 -5
  14. data/lib/orchestration/docker_compose/rabbitmq_service.rb +2 -3
  15. data/lib/orchestration/docker_compose.rb +1 -0
  16. data/lib/orchestration/environment.rb +19 -6
  17. data/lib/orchestration/errors.rb +1 -1
  18. data/lib/orchestration/file_helpers.rb +27 -4
  19. data/lib/orchestration/install_generator.rb +85 -20
  20. data/lib/orchestration/services/app/configuration.rb +9 -5
  21. data/lib/orchestration/services/app/healthcheck.rb +1 -22
  22. data/lib/orchestration/services/database/adapters/mysql2.rb +13 -2
  23. data/lib/orchestration/services/database/adapters/postgresql.rb +0 -1
  24. data/lib/orchestration/services/database/configuration.rb +68 -75
  25. data/lib/orchestration/services/database/healthcheck.rb +10 -1
  26. data/lib/orchestration/services/listener/configuration.rb +1 -1
  27. data/lib/orchestration/services/listener/healthcheck.rb +2 -2
  28. data/lib/orchestration/services/{configuration_base.rb → mixins/configuration_base.rb} +15 -13
  29. data/lib/orchestration/services/{healthcheck_base.rb → mixins/healthcheck_base.rb} +3 -2
  30. data/lib/orchestration/services/mixins/http_healthcheck.rb +38 -0
  31. data/lib/orchestration/services/mongo/configuration.rb +37 -63
  32. data/lib/orchestration/services/mongo/healthcheck.rb +3 -32
  33. data/lib/orchestration/services/rabbitmq/configuration.rb +11 -22
  34. data/lib/orchestration/services/rabbitmq/healthcheck.rb +2 -2
  35. data/lib/orchestration/services.rb +3 -2
  36. data/lib/orchestration/templates/Dockerfile.erb +8 -4
  37. data/lib/orchestration/templates/database.yml.erb +32 -0
  38. data/lib/orchestration/templates/deploy.mk.erb +2 -2
  39. data/lib/orchestration/templates/entrypoint.sh.erb +13 -4
  40. data/lib/orchestration/templates/env.erb +10 -2
  41. data/lib/orchestration/templates/healthcheck.rb.erb +56 -0
  42. data/lib/orchestration/templates/makefile_macros.mk.erb +108 -0
  43. data/lib/orchestration/templates/mongoid.yml.erb +18 -0
  44. data/lib/orchestration/templates/orchestration.mk.erb +242 -120
  45. data/lib/orchestration/templates/puma.rb.erb +19 -0
  46. data/lib/orchestration/templates/rabbitmq.yml.erb +12 -0
  47. data/lib/orchestration/templates/unicorn.rb.erb +5 -5
  48. data/lib/orchestration/terminal.rb +13 -15
  49. data/lib/orchestration/version.rb +1 -1
  50. data/lib/orchestration.rb +20 -2
  51. data/lib/tasks/orchestration.rake +3 -1
  52. data/orchestration.gemspec +3 -5
  53. metadata +23 -13
@@ -10,6 +10,8 @@ module Orchestration
10
10
  end
11
11
 
12
12
  def image
13
+ return mysql5_7 if gem_version < Gem::Version.new('0.4')
14
+
13
15
  'library/mysql'
14
16
  end
15
17
 
@@ -31,14 +33,23 @@ module Orchestration
31
33
 
32
34
  def environment
33
35
  {
34
- 'MYSQL_ROOT_PASSWORD' => 'password',
35
- 'MYSQL_TCP_PORT' => DockerCompose::DatabaseService::PORT.to_s
36
+ 'MYSQL_ROOT_PASSWORD' => 'password'
36
37
  }
37
38
  end
38
39
 
39
40
  def data_dir
40
41
  '/var/lib/mysql'
41
42
  end
43
+
44
+ private
45
+
46
+ def mysql5_7
47
+ 'library/mysql:5.7'
48
+ end
49
+
50
+ def gem_version
51
+ Gem::Version.new(Gem.loaded_specs['mysql2'].version)
52
+ end
42
53
  end
43
54
  end
44
55
  end
@@ -31,7 +31,6 @@ module Orchestration
31
31
 
32
32
  def environment
33
33
  {
34
- 'PGPORT' => DockerCompose::DatabaseService::PORT.to_s,
35
34
  'POSTGRES_PASSWORD' => 'password',
36
35
  'PGDATA' => data_dir
37
36
  }
@@ -8,114 +8,107 @@ module Orchestration
8
8
 
9
9
  self.service_name = 'database'
10
10
 
11
- attr_reader :adapter
11
+ def enabled?
12
+ defined?(::ActiveRecord)
13
+ end
12
14
 
13
- def initialize(env, service_name = nil)
14
- super
15
- @adapter = nil
16
- @settings = nil
17
- return unless defined?(ActiveRecord)
18
- return unless File.exist?(@env.database_configuration_path)
15
+ def friendly_config
16
+ return "[#{adapter.name}]" if adapter.name == 'sqlite3'
19
17
 
20
- @environments = parse(File.read(@env.database_configuration_path))
21
- setup
18
+ "[#{adapter.name}] #{host}:#{port}"
22
19
  end
23
20
 
24
- def friendly_config
25
- return "[#{@adapter.name}]" if @adapter.name == 'sqlite3'
21
+ def settings
22
+ {
23
+ adapter: adapter.name,
24
+ host: host,
25
+ port: port,
26
+ username: username,
27
+ password: password,
28
+ database: database
29
+ }.transform_keys(&:to_s)
30
+ end
26
31
 
27
- "[#{@adapter.name}] #{@settings['host']}:#{@settings['port']}"
32
+ def adapter
33
+ url_adapter = url_config['adapter']
34
+ file_adapter = file_config['adapter']
35
+
36
+ return adapter_by_name(url_adapter) unless url_adapter.nil?
37
+ return adapter_by_name(file_adapter) unless file_adapter.nil?
38
+ return adapter_by_name('sqlite3') if defined?(SQLite3)
39
+
40
+ nil
28
41
  end
29
42
 
30
43
  private
31
44
 
32
- def setup
33
- @adapter = adapter_for(base['adapter'])
34
- @settings = merged_settings
35
- return if @adapter.name == 'sqlite3'
36
- return unless %w[test development].include?(@env.environment)
45
+ def file_config
46
+ return {} unless File.exist?(@env.database_configuration_path)
37
47
 
38
- merge_port
48
+ yaml = ERB.new(File.read(env.database_configuration_path)).result
49
+ YAML.safe_load(yaml, [], [], true)[@env.environment] || {}
39
50
  end
40
51
 
41
- def merge_port
42
- return if environment.key?('port') || url_config['port']
52
+ def url_config
53
+ return {} if env.database_url.nil?
54
+
55
+ config = DatabaseUrl.to_active_record_hash(env.database_url)
56
+ &.transform_keys(&:to_s)
43
57
 
44
- @settings.merge!('port' => local_port) if @env.docker_compose_config?
58
+ # A quirk of DatabaseUrl is that if no "/path" is present then the
59
+ # `database` component is an empty string. In this unique case, we
60
+ # want `nil` instead so that we can delegate to a default.
61
+ config['database'] = nil if config['database']&.empty?
62
+ config
45
63
  end
46
64
 
47
- def merged_settings
48
- port = base['port'] || DockerCompose::DatabaseService::PORT
49
- base.merge(@adapter.credentials)
50
- .merge('scheme' => base['adapter'], 'port' => port)
65
+ def host
66
+ url_config['host'] || file_config['host'] || super
67
+ end
68
+
69
+ def port
70
+ url_config['port'] || file_config['port'] || super
71
+ end
72
+
73
+ def username
74
+ (
75
+ url_config['username'] ||
76
+ file_config['username'] ||
77
+ (adapter && adapter.credentials['username'])
78
+ )
51
79
  end
52
80
 
53
- def parse(content)
54
- yaml(erb(content))
81
+ def password
82
+ (
83
+ url_config['password'] ||
84
+ file_config['password'] ||
85
+ (adapter && adapter.credentials['password'])
86
+ )
55
87
  end
56
88
 
57
- def erb(content)
58
- ERB.new(content).result
89
+ def database
90
+ (
91
+ url_config['database'] ||
92
+ file_config['database'] ||
93
+ (adapter && adapter.credentials['database'])
94
+ )
59
95
  end
60
96
 
61
- def adapter_for(name)
97
+ def adapter_by_name(name)
62
98
  {
63
99
  'mysql2' => adapters::Mysql2,
64
100
  'mysql' => adapters::Mysql2,
65
101
  'postgresql' => adapters::Postgresql,
66
102
  'sqlite3' => adapters::Sqlite3
67
103
  }.fetch(name).new
68
- end
69
-
70
- def environment
71
- @environments.fetch(@env.environment)
72
104
  rescue KeyError
73
- raise UnknownEnvironmentError,
74
- I18n.t(
75
- 'orchestration.database.unknown_environment',
76
- environment: @env.environment
77
- )
78
- end
79
-
80
- def base
81
- environment.merge(url_config).merge('host' => host)
82
- end
83
-
84
- def host
85
- return nil if @adapter && @adapter.name == 'sqlite3'
86
- return url_config['host'] if url_config['host']
87
- return environment['host'] if environment.key?('host')
88
-
89
- super
105
+ Orchestration.error('database.unknown_adapter', adapter: name.inspect)
106
+ raise
90
107
  end
91
108
 
92
109
  def adapters
93
110
  Orchestration::Services::Database::Adapters
94
111
  end
95
-
96
- def default_port
97
- return {} if @adapter.name == 'sqlite3'
98
-
99
- { 'port' => @adapter.default_port }
100
- end
101
-
102
- def url_config
103
- return {} if @env.database_url.nil?
104
-
105
- uri = URI.parse(@env.database_url)
106
-
107
- {
108
- 'host' => uri.hostname,
109
- 'adapter' => uri.scheme,
110
- 'port' => uri.port
111
- }.merge(query_params(uri))
112
- end
113
-
114
- def query_params(uri)
115
- return {} if uri.query.nil?
116
-
117
- Hash[URI.decode_www_form(uri.query)]
118
- end
119
112
  end
120
113
  end
121
114
  end
@@ -9,7 +9,7 @@ module Orchestration
9
9
  dependencies 'active_record'
10
10
 
11
11
  def connect
12
- ActiveRecord::Base.establish_connection(@configuration.settings)
12
+ ActiveRecord::Base.establish_connection(settings)
13
13
  ActiveRecord::Base.connection
14
14
  end
15
15
 
@@ -22,6 +22,15 @@ module Orchestration
22
22
  def adapter_errors
23
23
  @configuration.adapter.errors
24
24
  end
25
+
26
+ def settings
27
+ return @configuration.settings unless @options[:init]
28
+
29
+ {
30
+ adapter: @configuration.adapter.name,
31
+ port: DockerCompose::DatabaseService::PORT
32
+ }.merge(@configuration.adapter.credentials)
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -11,7 +11,7 @@ module Orchestration
11
11
  end
12
12
 
13
13
  def friendly_config
14
- "[#{@service_name}] #{host}:#{local_port}"
14
+ "[#{@service_name}] #{host}:#{port}"
15
15
  end
16
16
  end
17
17
  end
@@ -7,11 +7,11 @@ module Orchestration
7
7
  include HealthcheckBase
8
8
 
9
9
  def connect
10
- Net::HTTP.start('localhost', @configuration.local_port)
10
+ Net::HTTP.start(@configuration.host, @configuration.port)
11
11
  end
12
12
 
13
13
  def connection_errors
14
- [Errno::ECONNREFUSED]
14
+ [Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL]
15
15
  end
16
16
  end
17
17
  end
@@ -3,7 +3,7 @@
3
3
  module Orchestration
4
4
  module Services
5
5
  module ConfigurationBase
6
- attr_reader :settings, :service_name, :env
6
+ attr_reader :service_name, :env
7
7
 
8
8
  def self.included(base)
9
9
  base.extend(ClassMethods)
@@ -28,20 +28,22 @@ module Orchestration
28
28
  end
29
29
 
30
30
  def host
31
- '127.0.0.1'
31
+ return '127.0.0.1' if %w[test development].include?(@env.environment)
32
+
33
+ @service_name
32
34
  end
33
35
 
34
- def local_port
35
- return ENV.fetch('LISTEN_PORT', '3000').to_i if @service_name == 'app'
36
-
37
- @env.docker_compose_config
38
- .fetch('services')
39
- .fetch(@service_name)
40
- .fetch('ports')
41
- .first
42
- .partition(':')
43
- .first
44
- .to_i
36
+ def port
37
+ return @env.app_port if @service_name == 'app'
38
+
39
+ local, _, remote = @env.docker_compose_config
40
+ .fetch('services')
41
+ .fetch(@service_name)
42
+ .fetch('ports')
43
+ .first
44
+ .partition(':')
45
+
46
+ (@env.environment == 'production' ? remote : local).to_i
45
47
  end
46
48
 
47
49
  def yaml(content)
@@ -17,7 +17,7 @@ module Orchestration
17
17
  env ||= Environment.new
18
18
  terminal ||= Terminal.new(env.settings)
19
19
  name = options.delete(:service_name)
20
- check = ServiceCheck.new(new(env, name), terminal, options)
20
+ check = ServiceCheck.new(new(env, name, options), terminal, options)
21
21
 
22
22
  exit 1 if !check.run && exit_on_error
23
23
  end
@@ -35,7 +35,8 @@ module Orchestration
35
35
  end
36
36
  end
37
37
 
38
- def initialize(env, service_name = nil)
38
+ def initialize(env, service_name = nil, options = {})
39
+ @options = options
39
40
  @configuration = configuration_class.new(env, service_name)
40
41
  end
41
42
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orchestration
4
+ module Services
5
+ module HTTPHealthcheck
6
+ def connect
7
+ code = Net::HTTP.get_response(
8
+ URI("http://#{@configuration.host}:#{@configuration.port}")
9
+ ).code
10
+ connection_error(code) if connection_error?(code)
11
+ connection_error(code) unless connection_success?(code)
12
+ end
13
+
14
+ def connection_errors
15
+ [Errno::ECONNREFUSED, HTTPConnectionError]
16
+ end
17
+
18
+ private
19
+
20
+ def connection_error(code)
21
+ raise HTTPConnectionError,
22
+ I18n.t('orchestration.http.connection_error', code: code)
23
+ end
24
+
25
+ def connection_error?(code)
26
+ %w[502 503 500].include?(code)
27
+ end
28
+
29
+ def connection_success?(_code)
30
+ # Override if specific success codes needed, otherwise default to true
31
+ # (i.e. an error code [as defined above] was not returned so we assume
32
+ # success).
33
+
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -8,95 +8,69 @@ module Orchestration
8
8
 
9
9
  self.service_name = 'mongo'
10
10
 
11
- CONFIG_KEYS = %w[clients sessions hosts].freeze
12
-
13
- def initialize(env, service_name = nil)
14
- super
15
- @settings = nil
16
- return unless defined?(Mongoid)
17
- return unless File.exist?(@env.mongoid_configuration_path)
18
-
19
- @settings = if clients_key.nil?
20
- hosts_config # Host was configured at top level.
21
- else
22
- { clients_key => hosts_config }
23
- end
11
+ def enabled?
12
+ defined?(::Mongoid)
24
13
  end
25
14
 
26
15
  def friendly_config
27
- "[mongoid] #{host}:#{port}/#{database}"
16
+ "[mongoid] mongodb://#{host}:#{port}/#{database}"
28
17
  end
29
18
 
30
- def port
31
- return url_config[:port] unless url_config.nil?
19
+ def database
20
+ return url_config['database'] unless @env.mongo_url.nil?
32
21
 
33
- DockerCompose::MongoService::PORT
22
+ file_config.fetch('database', default_database)
34
23
  end
35
24
 
36
25
  def host
37
- return url_config[:host] unless url_config.nil?
26
+ return url_config['host'] unless @env.mongo_url.nil?
38
27
 
39
28
  super
40
29
  end
41
30
 
42
- private
31
+ def port
32
+ return url_config['port'] unless @env.mongo_url.nil?
43
33
 
44
- def hosts_config
45
- {
46
- 'default' => {
47
- 'hosts' => ["#{host}:#{port}"],
48
- 'database' => database
49
- }
50
- }
34
+ super
51
35
  end
52
36
 
53
- def url_config
54
- return nil unless ENV.key?('MONGO_URL')
37
+ private
55
38
 
56
- host, _, port = ENV['MONGO_URL'].rpartition('/').first.partition(':')
57
- _, _, database = ENV['MONGO_URL'].rpartition('/')
58
- { host: host, port: port.empty? ? '27017' : port, database: database }
39
+ def default_database
40
+ "#{@env.environment}db"
59
41
  end
60
42
 
61
- def database
62
- return url_config[:database] unless url_config.nil?
63
-
64
- env_config = config.fetch(@env.environment)
65
- return env_config.fetch('database') if env_config.key?('database')
66
-
67
- bad_config_error if clients_key.nil?
68
- merged_config(env_config)
69
- end
43
+ def url_config
44
+ uri = URI.parse(@env.mongo_url)
45
+ unless uri.scheme == 'mongodb'
46
+ raise ArgumentError, 'MONGO_URL protocol must be mongodb://'
47
+ end
70
48
 
71
- def merged_config(env_config)
72
- env_config
73
- .fetch(clients_key)
74
- .fetch('default')
75
- .fetch('database')
49
+ url_config_structure(uri)
76
50
  end
77
51
 
78
- def clients_key
79
- env_config = config.fetch(@env.environment)
52
+ def url_config_structure(uri)
53
+ hosts = uri.host.split(',')
54
+ database = uri.path.partition('/').last
80
55
 
81
- # Support older Mongoid versions
82
- CONFIG_KEYS.each do |key|
83
- return key if env_config.key?(key)
84
- end
85
-
86
- nil
56
+ {
57
+ 'user' => uri.user,
58
+ 'password' => uri.password,
59
+ 'host' => hosts.first,
60
+ 'port' => uri.port || Services::Mongo::PORT,
61
+ 'database' => database == '' ? default_database : database
62
+ }
87
63
  end
88
64
 
89
- def config
90
- @config ||= yaml(File.read(@env.mongoid_configuration_path))
91
- end
65
+ def file_config
66
+ return {} unless File.exist?(@env.mongoid_configuration_path)
67
+
68
+ yaml = File.read(@env.mongoid_configuration_path)
69
+ config = YAML.safe_load(yaml, [], [], true)
70
+ env = config.fetch(@env.environment, nil)
71
+ return {} if env.nil?
92
72
 
93
- def bad_config_error
94
- raise ArgumentError,
95
- I18n.t(
96
- 'orchestration.mongo.bad_config',
97
- path: @env.mongoid_configuration_path,
98
- expected: CONFIG_KEYS.join(', ')
99
- )
73
+ env.fetch('clients', env.fetch('sessions', {})).fetch('default', {})
100
74
  end
101
75
  end
102
76
  end
@@ -5,39 +5,10 @@ module Orchestration
5
5
  module Mongo
6
6
  class Healthcheck
7
7
  include HealthcheckBase
8
+ include HTTPHealthcheck
8
9
 
9
- dependencies 'mongoid'
10
-
11
- def connection_errors
12
- return [Moped::Errors::ConnectionFailure] if defined?(Moped)
13
-
14
- [::Mongo::Error::NoServerAvailable]
15
- end
16
-
17
- def connect
18
- silence_warnings
19
-
20
- # REVIEW: For some reason this is extremely slow. Worth trying
21
- # to see if there's a faster way to fail.
22
- Mongoid.load_configuration(@configuration.settings)
23
- !default_client.database_names.empty?
24
- end
25
-
26
- private
27
-
28
- def default_client
29
- return Mongoid.default_client if Mongoid.respond_to?(:default_client)
30
-
31
- # Support older versions of Mongoid
32
- Mongoid.default_session
33
- end
34
-
35
- def silence_warnings
36
- if defined?(Moped)
37
- Moped.logger = Logger.new(devnull)
38
- else
39
- Mongoid.logger = Logger.new(devnull)
40
- end
10
+ def connection_success?(code)
11
+ code == '200'
41
12
  end
42
13
  end
43
14
  end
@@ -8,41 +8,30 @@ module Orchestration
8
8
 
9
9
  self.service_name = 'rabbitmq'
10
10
 
11
- def initialize(env, service_name = nil)
12
- super
13
- @settings = nil
14
- return unless defined?(RabbitMQ)
15
- return unless File.exist?(@env.rabbitmq_configuration_path)
16
-
17
- if ENV.key?('RABBITMQ_URL')
18
- @settings = from_url
19
- return
20
- end
21
-
22
- @settings = config.fetch(@env.environment)
23
- @settings.merge!('port' => PORT) unless @settings.key?('port')
11
+ def enabled?
12
+ defined?(::Bunny)
24
13
  end
25
14
 
26
15
  def friendly_config
27
16
  "[bunny] amqp://#{host}:#{port}"
28
17
  end
29
18
 
30
- private
31
-
32
- def config
33
- yaml(File.read(@env.rabbitmq_configuration_path))
34
- end
35
-
36
19
  def host
37
- @settings.fetch('host')
20
+ return from_url['host'] unless @env.rabbitmq_url.nil?
21
+
22
+ super
38
23
  end
39
24
 
40
25
  def port
41
- @settings.fetch('port')
26
+ return from_url['port'] unless @env.rabbitmq_url.nil?
27
+
28
+ super
42
29
  end
43
30
 
31
+ private
32
+
44
33
  def from_url
45
- uri = URI.parse(ENV.fetch('RABBITMQ_URL'))
34
+ uri = URI.parse(@env.rabbitmq_url)
46
35
  { 'host' => uri.host, 'port' => uri.port || 5672 }
47
36
  end
48
37
  end
@@ -17,8 +17,8 @@ module Orchestration
17
17
  end
18
18
 
19
19
  def connect
20
- host = @configuration.settings.fetch('host')
21
- port = @configuration.settings.fetch('port')
20
+ host = @configuration.host
21
+ port = @configuration.port
22
22
  connection = Bunny.new("amqp://#{host}:#{port}", log_file: devnull)
23
23
  connection.start
24
24
  connection.stop
@@ -5,8 +5,9 @@ module Orchestration
5
5
  end
6
6
  end
7
7
 
8
- require 'orchestration/services/configuration_base'
9
- require 'orchestration/services/healthcheck_base'
8
+ require 'orchestration/services/mixins/configuration_base'
9
+ require 'orchestration/services/mixins/healthcheck_base'
10
+ require 'orchestration/services/mixins/http_healthcheck'
10
11
 
11
12
  require 'orchestration/services/app'
12
13
  require 'orchestration/services/database'
@@ -17,8 +17,12 @@ RUN bundle install --without development test --deployment
17
17
  COPY .build/package.json .build/yarn.lock ./
18
18
  RUN . /root/.bashrc && yarn install
19
19
  <% end %>
20
- COPY entrypoint.sh /
21
- ADD .build/context.tar.gz .
20
+ ADD .build/context.tar .
22
21
  <% if defined?(Webpacker) %>RUN . /root/.bashrc && NODE_ENV=production RAILS_ENV=production yarn install && NODE_ENV=production RAILS_ENV=production SECRET_KEY_BASE=abc123 bundle exec rake assets:precompile<% elsif Rake::Task.tasks.map(&:name).include?('assets:precompile') %>RUN NODE_ENV=production RAILS_ENV=production SECRET_KEY_BASE=abc123 bundle exec rake assets:precompile<% end %>
23
- ENTRYPOINT ["/entrypoint.sh"]
24
- CMD ["bundle", "exec", "unicorn", "-c", "/app/config/unicorn.rb"]
22
+ HEALTHCHECK --interval=<%= healthcheck['interval'] %> \
23
+ --timeout=<%= healthcheck['timeout'] %> \
24
+ --start-period=<%= healthcheck['start_period'] %> \
25
+ --retries=<%= healthcheck['retries'] %> \
26
+ CMD <%= healthcheck['test'].to_json %>
27
+ ENTRYPOINT <%= entrypoint.to_json %>
28
+ CMD <%= command.to_json %>