orchestration 0.3.17 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>