docker-swarm 0.3.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b46f33638fb88f8c9fca4a85747f67e266525933e4b22d38bb47e2ce1556a552
4
- data.tar.gz: b080b742324d91eb1570af605b05539da67284fb76631faae04583f2577edad3
3
+ metadata.gz: a5c66a1153f6aa54787f22d1281e4b8d1932a951f70000ff0a3af72e79c9be7b
4
+ data.tar.gz: 6469d6833f97509511d0bfffda44bbbdc650fa1c928d624efef79b5ba71221ef
5
5
  SHA512:
6
- metadata.gz: 17f03f903b19273e822feb4a389ff9b862d034607f032c3b6112838b1ddf5f38927dabad4001214f6ad9ce6a06f8661939b38fb2fa3dda8a0b1975246da199fa
7
- data.tar.gz: 44edbe59ffa431b2cf78735ca138c2fd4c41d0d57dda543509d9de8a417343c525d1a3493d549fa4e2ebfc63948491a2994917df493683c25f8c33e2f795eadf
6
+ metadata.gz: bc5f0fa14f81cf2d9da225c586458a155e8ac1042d5bc3d190b17a2f4c151d1239f2dd6046e1aa078d15ddea59a322476cdce5827945f8ad74a9fdd9030d79a5
7
+ data.tar.gz: 3bd4011e41f199280010a1cb716735ef72ba54926c33c36425fb483b75611821a0ce12b07b0c731b88e0bfc63f7cfe66e84fe99c42b60c8f1ecf2745ed86feac
data/README.md CHANGED
@@ -5,54 +5,135 @@
5
5
 
6
6
  `docker-swarm` es un ORM ligero y cliente API robusto para interactuar con Docker Swarm desde Ruby. Diseñado para sentirse familiar a los desarrolladores de Rails, utiliza `ActiveModel` para ofrecer una interfaz limpia y potente con estándares de observabilidad de Wispro.
7
7
 
8
- ## 🚀 Inicio Rápido
8
+ ## Instalacion
9
+
10
+ Agrega la gema a tu `Gemfile`:
11
+
12
+ ```ruby
13
+ gem 'docker-swarm', '~> 0.5'
14
+ ```
15
+
16
+ Y ejecuta:
17
+
18
+ ```bash
19
+ bundle install
20
+ ```
21
+
22
+ ## Quick Start
9
23
 
10
24
  ```ruby
11
25
  require 'docker_swarm'
12
26
 
13
- # Configurar (opcional, usa defaults)
27
+ # Configurar (opcional, usa defaults razonables)
14
28
  DockerSwarm.configure do |config|
15
29
  config.socket_path = "unix:///var/run/docker.sock"
16
- config.log_level = Logger::INFO
17
- config.read_timeout = 30
30
+ config.log_level = Logger::INFO
18
31
  config.max_retries = 3
19
32
  end
20
33
 
21
- # Listar servicios (con soporte para Indifferent Access en arrays)
34
+ # Verificar conectividad
35
+ DockerSwarm::System.up # => "OK"
36
+
37
+ # Listar servicios
22
38
  services = DockerSwarm::Service.all
23
39
  services.each { |s| puts "#{s.ID}: #{s.Spec[:Name]}" }
40
+ ```
41
+
42
+ ## Uso
24
43
 
25
- # Crear un nuevo servicio
44
+ ### Crear un servicio
45
+
46
+ ```ruby
26
47
  service = DockerSwarm::Service.create(
27
48
  Name: "my-webapp",
28
- Spec: {
29
- TaskTemplate: {
30
- ContainerSpec: { Image: "nginx:latest" }
31
- }
49
+ TaskTemplate: {
50
+ ContainerSpec: { Image: "nginx:latest" }
32
51
  }
33
52
  )
53
+ puts service.ID
54
+ ```
34
55
 
35
- # Obtener logs (stdout y stderr habilitados por defecto)
36
- puts service.logs
56
+ ### Actualizar (maneja Version.Index automaticamente)
57
+
58
+ ```ruby
59
+ service = DockerSwarm::Service.find("svc-id")
60
+ service.update(Mode: { Replicated: { Replicas: 3 } })
37
61
  ```
38
62
 
39
- ## 🛠 Características Clave
63
+ ### Eliminar
64
+
65
+ ```ruby
66
+ service.destroy # graceful: retorna nil si ya no existe
67
+ ```
68
+
69
+ ### Logs
70
+
71
+ ```ruby
72
+ puts service.logs(stdout: 1, stderr: 1)
73
+ ```
74
+
75
+ ### Filtros
76
+
77
+ ```ruby
78
+ DockerSwarm::Service.all(label: ["env=production"])
79
+ DockerSwarm::Node.all(role: ["manager"])
80
+ DockerSwarm::Container.all(status: ["running"])
81
+ ```
82
+
83
+ ### Sistema y Cluster
84
+
85
+ ```ruby
86
+ DockerSwarm::System.info # informacion del daemon
87
+ DockerSwarm::System.version # version de Docker
88
+ DockerSwarm::System.df # uso de disco
89
+ DockerSwarm::Swarm.show # informacion del cluster
90
+ ```
91
+
92
+ ### Manejo de errores
93
+
94
+ ```ruby
95
+ begin
96
+ DockerSwarm::Service.create(Name: "web", TaskTemplate: { ... })
97
+ rescue DockerSwarm::Conflict => e
98
+ puts "Nombre duplicado"
99
+ rescue DockerSwarm::Communication => e
100
+ puts "Docker inalcanzable: #{e.message}"
101
+ end
102
+ ```
103
+
104
+ ## Configuracion
105
+
106
+ ```ruby
107
+ DockerSwarm.configure do |config|
108
+ config.socket_path = "unix:///var/run/docker.sock" # o http://host:port
109
+ config.logger = Logger.new($stdout)
110
+ config.log_level = Logger::INFO
111
+ config.read_timeout = 60 # segundos
112
+ config.write_timeout = 60
113
+ config.connect_timeout = 10
114
+ config.max_retries = 3
115
+ end
116
+ ```
117
+
118
+ Ver [Guia de Configuracion](docs/configuration.md) para opciones avanzadas, Rails integration y observabilidad.
119
+
120
+ ## Documentacion
40
121
 
41
- - **Observabilidad Wispro**: Logging estructurado (KV) con `component`, `event`, `source` y `duration_ms` usando reloj monotónico.
42
- - **Seguridad**: Enmascaramiento automático de secretos (`password`, `token`, etc.) en los logs.
43
- - **Deep Indifferent Access**: Acceso a atributos mediante símbolos o strings, incluso en resultados de listados (`.all`).
44
- - **Resiliencia**: Gestión inteligente de timeouts (`read`, `write`, `connect`) y reintentos automáticos para errores de red.
45
- - **Mapeo PascalCase**: Mantiene la fidelidad con los atributos de Docker (e.g., `s.ID`, `s.Spec`) evitando transformaciones costosas.
46
- - **ActiveModel Ready**: Soporta validaciones, serialización JSON y comportamientos estándar de modelos Ruby.
122
+ - [Modelos (ORM)](docs/models.md) Todos los modelos, concerns y ciclo de vida
123
+ - [Configuracion](docs/configuration.md) Opciones, observabilidad y seguridad
124
+ - [Manejo de Errores](docs/errors.md) Jerarquia de excepciones y uso
125
+ - [Cliente API](docs/api.md) Acceso de bajo nivel y middlewares
126
+ - [Testing](docs/testing.md) Estrategias de mocking para tus tests
47
127
 
48
- ## 🤝 Contribuir
128
+ ## Contribuir
49
129
 
50
- Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las guías de desarrollo y asegúrate de que todos los tests pasen antes de enviar un PR.
130
+ Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las guias de desarrollo.
51
131
 
52
132
  ```bash
53
- bundle exec rspec
133
+ bundle exec rspec # tests
134
+ bundle exec rubocop -a # linting
54
135
  ```
55
136
 
56
- ## 📄 Licencia
137
+ ## Licencia
57
138
 
58
- Este proyecto está bajo la licencia MIT.
139
+ Este proyecto esta bajo la licencia MIT.
@@ -3,12 +3,19 @@
3
3
  module DockerSwarm
4
4
  class Base
5
5
  include ActiveModel::Model
6
+ include Concerns::Inspectable
6
7
 
7
8
  class << self
8
9
  def resource_name
9
10
  name.demodulize.downcase.pluralize
10
11
  end
11
12
 
13
+ # Cache of attributes already defined for this class
14
+ # @return [Set]
15
+ def defined_attributes
16
+ @defined_attributes ||= Set.new([ :validation_context, :errors, :context_for_validation ])
17
+ end
18
+
12
19
  def routes
13
20
  Api::ENDPOINTS[resource_name.to_sym]
14
21
  end
@@ -156,8 +163,10 @@ module DockerSwarm
156
163
  private
157
164
 
158
165
  def _define_dynamic_accessor(key)
159
- return if self.class.method_defined?("#{key}=")
166
+ return if self.class.defined_attributes.include?(key.to_sym)
167
+
160
168
  self.class.send(:attr_accessor, key)
169
+ self.class.defined_attributes.add(key.to_sym)
161
170
  end
162
171
 
163
172
  def _valid_attribute_name?(name)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerSwarm
4
+ module Concerns
5
+ module Inspectable
6
+ extend ActiveSupport::Concern
7
+
8
+ def inspect
9
+ inspection = if respond_to?(:ID) && ID.present?
10
+ "ID: #{ID}"
11
+ else
12
+ "not persisted"
13
+ end
14
+
15
+ # Intentar añadir el nombre si existe
16
+ name_attr = attributes["Name"] || (respond_to?(:Name) ? Name : nil)
17
+ inspection += ", Name: #{name_attr}" if name_attr.present?
18
+
19
+ # Intentar añadir la imagen para servicios/contenedores
20
+ spec = attributes["Spec"] || (respond_to?(:Spec) ? Spec : nil)
21
+ image = spec&.dig("TaskTemplate", "ContainerSpec", "Image") || attributes["Image"] || (respond_to?(:Image) ? Image : nil)
22
+ inspection += ", Image: #{image}" if image.present?
23
+
24
+ "#<#{self.class.name} #{inspection}>"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerSwarm
4
+ module Concerns
5
+ module Loggable
6
+ extend ActiveSupport::Concern
7
+
8
+ # Fetches logs for the resource
9
+ # @param query_params [Hash] Query parameters for the logs endpoint (stdout, stderr, follow, etc.)
10
+ # @return [String] The raw log stream
11
+ def logs(query_params = { stdout: 1, stderr: 1 })
12
+ Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,18 +4,33 @@ require "logger"
4
4
 
5
5
  module DockerSwarm
6
6
  class Configuration
7
- attr_accessor :socket_path, :logger, :log_level,
8
- :read_timeout, :write_timeout, :connect_timeout,
9
- :max_retries
7
+ attr_accessor :socket_path, :logger, :log_level
8
+ attr_reader :read_timeout, :write_timeout, :connect_timeout, :max_retries
10
9
 
11
10
  def initialize
12
11
  @socket_path = "unix:///var/run/docker.sock"
13
12
  @logger = Logger.new($stdout)
14
13
  @log_level = Logger::INFO
15
- @read_timeout = 60
16
- @write_timeout = 60
17
- @connect_timeout = 10
14
+ @read_timeout = 60.0
15
+ @write_timeout = 60.0
16
+ @connect_timeout = 10.0
18
17
  @max_retries = 3
19
18
  end
19
+
20
+ def read_timeout=(val)
21
+ @read_timeout = val&.to_f
22
+ end
23
+
24
+ def write_timeout=(val)
25
+ @write_timeout = val&.to_f
26
+ end
27
+
28
+ def connect_timeout=(val)
29
+ @connect_timeout = val&.to_f
30
+ end
31
+
32
+ def max_retries=(val)
33
+ @max_retries = val&.to_i
34
+ end
20
35
  end
21
36
  end
@@ -11,47 +11,43 @@ module DockerSwarm
11
11
 
12
12
  def request(options = {})
13
13
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
-
15
- log_event("request_started",
14
+
15
+ log_event("request_started",
16
16
  level: :debug,
17
- data: { method: options[:method], path: options[:path] })
17
+ data: options.merge(path: options[:path]))
18
18
 
19
19
  # Combinar opciones por defecto de la gema con las de la petición
20
20
  request_options = {
21
21
  idempotent: true,
22
- retry_errors: [Excon::Error::Socket, Excon::Error::Timeout],
23
- read_timeout: DockerSwarm.configuration.read_timeout,
24
- write_timeout: DockerSwarm.configuration.write_timeout,
25
- connect_timeout: DockerSwarm.configuration.connect_timeout,
26
- retries: DockerSwarm.configuration.max_retries
22
+ retry_errors: [ Excon::Error::Socket, Excon::Error::Timeout ],
23
+ read_timeout: DockerSwarm.configuration.read_timeout.to_f,
24
+ write_timeout: DockerSwarm.configuration.write_timeout.to_f,
25
+ connect_timeout: DockerSwarm.configuration.connect_timeout.to_f,
26
+ retries: DockerSwarm.configuration.max_retries.to_i
27
27
  }.merge(options)
28
28
 
29
29
  response = client.request(request_options)
30
-
31
- log_event("request_success",
32
- data: {
33
- method: request_options[:method],
34
- path: request_options[:path],
35
- status: response.status,
36
- duration_ms: calculate_duration(start_time)
37
- })
38
-
30
+
31
+ log_event("request_success",
32
+ data: options.merge(
33
+ status: response.status,
34
+ duration_ms: calculate_duration(start_time)
35
+ ))
36
+
39
37
  response.body
40
38
  rescue => e
41
39
  # Excon suele envolver excepciones de middleware en Excon::Error::Socket.
42
40
  # Intentamos recuperar la causa original si es una de nuestras excepciones.
43
41
  actual_error = (e.respond_to?(:cause) && e.cause&.class&.name&.include?("DockerSwarm::Error")) ? e.cause : e
44
-
45
- log_event("request_failure",
42
+
43
+ log_event("request_failure",
46
44
  level: :error,
47
- data: {
48
- method: options[:method],
49
- path: options[:path],
45
+ data: options.merge(
50
46
  error: actual_error.class.name,
51
47
  message: actual_error.message,
52
- duration_ms: calculate_duration(start_time)
53
- })
54
-
48
+ duration_ms: calculate_duration(start_time)
49
+ ))
50
+
55
51
  if actual_error.class.name.include?("DockerSwarm::Error")
56
52
  raise actual_error
57
53
  elsif actual_error.is_a?(Excon::Error::Socket)
@@ -69,20 +65,11 @@ module DockerSwarm
69
65
  return if level == :debug && logger.level > Logger::DEBUG
70
66
 
71
67
  log_block = proc do
72
- begin
73
- payload = {
74
- component: "docker_swarm.connection",
75
- event: event,
76
- source: "http"
77
- }.merge(data)
78
-
79
- payload.map do |k, v|
80
- val = k.to_s =~ /password|token|api_key|auth|secret/i ? "[FILTERED]" : v
81
- "#{k}=#{val}"
82
- end.join(" ")
83
- rescue
84
- "component=docker_swarm.connection event=logging_error"
85
- end
68
+ LogHelper.format_kv({
69
+ component: "docker_swarm.connection",
70
+ event: event,
71
+ source: "http"
72
+ }.merge(data))
86
73
  end
87
74
 
88
75
  if level == :debug
@@ -109,9 +96,9 @@ module DockerSwarm
109
96
 
110
97
  def client
111
98
  debug_enabled = logger&.level == Logger::DEBUG
112
-
113
- options = {
114
- middlewares: common_middlewares,
99
+
100
+ options = {
101
+ middlewares: common_middlewares,
115
102
  logger: logger,
116
103
  debug_request: debug_enabled,
117
104
  debug_response: debug_enabled,
@@ -121,9 +108,9 @@ module DockerSwarm
121
108
 
122
109
  @client ||= if socket_path.start_with?("unix://")
123
110
  Excon.new("unix:///", options.merge(socket: socket_path.sub(/^unix:\/\//, "")))
124
- else
111
+ else
125
112
  Excon.new(socket_path, options)
126
- end
113
+ end
127
114
  end
128
115
  end
129
116
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DockerSwarm
4
+ # Helper module to centralize logging logic and formatting
5
+ module LogHelper
6
+ SENSITIVE_KEYS = /password|token|api_key|auth|secret|data/i.freeze
7
+
8
+ # Formats a hash into a KV structured string with sensitive data masking
9
+ # @param payload [Hash] The data to format
10
+ # @return [String] KV formatted string
11
+ def self.format_kv(payload)
12
+ payload.map do |k, v|
13
+ val = k.to_s =~ SENSITIVE_KEYS ? "[FILTERED]" : v
14
+ "#{k}=#{val}"
15
+ end.join(" ")
16
+ rescue
17
+ "event=logging_error"
18
+ end
19
+ end
20
+ end
@@ -40,7 +40,7 @@ module DockerSwarm
40
40
  return unless logger
41
41
 
42
42
  begin
43
- payload = {
43
+ kv_string = LogHelper.format_kv({
44
44
  component: "docker_swarm.middleware.error_handler",
45
45
  event: "business_error",
46
46
  source: "http",
@@ -48,12 +48,7 @@ module DockerSwarm
48
48
  message: message,
49
49
  method: env[:method],
50
50
  path: env[:path]
51
- }
52
-
53
- kv_string = payload.map do |k, v|
54
- val = k.to_s =~ /password|token|api_key|auth|secret/i ? "[FILTERED]" : v
55
- "#{k}=#{val}"
56
- end.join(" ")
51
+ })
57
52
 
58
53
  logger.error(kv_string)
59
54
  rescue
@@ -7,7 +7,7 @@ module DockerSwarm
7
7
  if env[:body] && !env[:body].is_a?(String)
8
8
  content_type = (env[:headers]["Content-Type"] || env[:headers][:content_type]).to_s
9
9
  env[:body] = serialize_body(env[:body], content_type)
10
-
10
+
11
11
  if content_type.blank? || content_type.include?("application/json")
12
12
  env[:headers]["Content-Type"] ||= "application/json"
13
13
  end
@@ -12,7 +12,7 @@ module DockerSwarm
12
12
  env[:response][:body] = parse_json(body)
13
13
  end
14
14
  end
15
-
15
+
16
16
  @stack.response_call(env)
17
17
  end
18
18
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Swarm Config
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Config
4
6
  class Config < Base
5
7
  include Concerns::Creatable
6
8
  include Concerns::Deletable
@@ -1,19 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Container
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Container
4
6
  class Container < Base
5
7
  include Concerns::Deletable
8
+ include Concerns::Loggable
6
9
 
10
+ # Starts the container
11
+ # @return [Boolean] true if successful
7
12
  def start
8
13
  Api.request(action: self.class.routes[:start], arguments: { id: self.ID })
14
+ true
9
15
  end
10
16
 
17
+ # Stops the container
18
+ # @return [Boolean] true if successful
11
19
  def stop
12
20
  Api.request(action: self.class.routes[:stop], arguments: { id: self.ID })
13
- end
14
-
15
- def logs(query_params = { stdout: 1, stderr: 1 })
16
- Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
21
+ true
17
22
  end
18
23
  end
19
24
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Image
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Image
4
6
  class Image < Base
5
7
  include Concerns::Creatable
6
8
  include Concerns::Deletable
@@ -5,14 +5,5 @@ module DockerSwarm
5
5
  include Concerns::Creatable
6
6
  include Concerns::Updatable
7
7
  include Concerns::Deletable
8
-
9
- validate :validate_name_presence
10
-
11
- private
12
-
13
- def validate_name_presence
14
- name = attributes["Name"] || (respond_to?(:Name) ? Name : nil)
15
- errors.add(:Name, "can't be blank") if name.blank?
16
- end
17
8
  end
18
9
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Swarm Node
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Node
4
6
  class Node < Base
5
7
  include Concerns::Updatable
6
8
  include Concerns::Deletable
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Swarm Secret
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Secret
4
6
  class Secret < Base
5
7
  include Concerns::Creatable
6
8
  include Concerns::Deletable
@@ -7,12 +7,6 @@ module DockerSwarm
7
7
  include Concerns::Creatable
8
8
  include Concerns::Updatable
9
9
  include Concerns::Deletable
10
-
11
- # Fetches logs for the service
12
- # @param query_params [Hash] Query parameters for the logs endpoint (stdout, stderr, follow, etc.)
13
- # @return [String] The raw log stream
14
- def logs(query_params = { stdout: 1, stderr: 1 })
15
- Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
16
- end
10
+ include Concerns::Loggable
17
11
  end
18
12
  end
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Access point for Docker Swarm cluster information
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Swarm
4
6
  class Swarm < Base
7
+ # Override resource name to match API endpoint
8
+ # @return [String]
5
9
  def self.resource_name
6
10
  "swarm"
7
11
  end
8
12
 
13
+ # Returns information about the swarm
14
+ # @return [Hash] Raw swarm data
9
15
  def self.show
10
16
  Api.request(action: routes[:show])
11
17
  end
@@ -1,23 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Access point for Docker System information and operations
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/System
4
6
  class System < Base
7
+ # Override resource name to match API endpoint
8
+ # @return [String]
5
9
  def self.resource_name
6
10
  "system"
7
11
  end
8
12
 
13
+ # Returns system information
14
+ # @return [Hash]
9
15
  def self.info
10
16
  Api.request(action: routes[:info])
11
17
  end
12
18
 
19
+ # Returns Docker version information
20
+ # @return [Hash]
13
21
  def self.version
14
22
  Api.request(action: routes[:version])
15
23
  end
16
24
 
25
+ # Checks if the Docker daemon is responding
26
+ # @return [String] "OK" if up
17
27
  def self.up
18
28
  Api.request(action: routes[:up])
19
29
  end
20
30
 
31
+ # Returns system-wide data usage information
32
+ # @return [Hash]
21
33
  def self.df
22
34
  Api.request(action: routes[:df])
23
35
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
+ # Represents a Docker Swarm Task
5
+ # @see https://docs.docker.com/engine/api/v1.41/#tag/Task
4
6
  class Task < Base
5
- def logs(query_params = { stdout: 1, stderr: 1 })
6
- Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
7
- end
7
+ include Concerns::Loggable
8
8
  end
9
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DockerSwarm
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end