docker-swarm 0.3.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b46f33638fb88f8c9fca4a85747f67e266525933e4b22d38bb47e2ce1556a552
4
- data.tar.gz: b080b742324d91eb1570af605b05539da67284fb76631faae04583f2577edad3
3
+ metadata.gz: 78ad878518bedd7c1ac7355efbb6372b49f2721fd7db5a3b0536b0872ed9f3ea
4
+ data.tar.gz: 6a5b9b06a1c43e8735d111cda5e9eb65820101fe515832fdf64647834310ad12
5
5
  SHA512:
6
- metadata.gz: 17f03f903b19273e822feb4a389ff9b862d034607f032c3b6112838b1ddf5f38927dabad4001214f6ad9ce6a06f8661939b38fb2fa3dda8a0b1975246da199fa
7
- data.tar.gz: 44edbe59ffa431b2cf78735ca138c2fd4c41d0d57dda543509d9de8a417343c525d1a3493d549fa4e2ebfc63948491a2994917df493683c25f8c33e2f795eadf
6
+ metadata.gz: 762b554eb9e1321d1cb2cb82867809b816ba7e7a84ee55e1e34b571b0942aba9e13020d8b7019dc40c70d4457b3307f9d72ca5cc98cd5de26207c9bf9ce70c1b
7
+ data.tar.gz: e092c1f28b3de38986f3f5e207dd1aafeac8449448e861675aa0be88ee4763f71aa663fc9ee8c1b3bafcfe1997b3917a0924ca9dea777b484b5163bd4b197d45
@@ -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/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.4.0"
5
5
  end
data/lib/docker_swarm.rb CHANGED
@@ -6,6 +6,7 @@ require "active_model"
6
6
  require "excon"
7
7
  require "json"
8
8
  require "logger"
9
+ require "set"
9
10
 
10
11
  module DockerSwarm
11
12
  class << self
@@ -14,11 +15,11 @@ module DockerSwarm
14
15
  def configure
15
16
  @configuration ||= Configuration.new
16
17
  yield(@configuration) if block_given?
17
-
18
+
18
19
  if @configuration.logger.respond_to?(:level=)
19
20
  @configuration.logger.level = @configuration.log_level
20
21
  end
21
-
22
+
22
23
  @connection = nil
23
24
  end
24
25
 
@@ -35,6 +36,7 @@ end
35
36
 
36
37
  # Primero cargamos la configuración porque la clase DockerSwarm la usa arriba
37
38
  require_relative "docker_swarm/configuration"
39
+ require_relative "docker_swarm/log_helper"
38
40
  require_relative "docker_swarm/version"
39
41
  require_relative "docker_swarm/errors"
40
42
  require_relative "docker_swarm/middleware/request_encoder"
@@ -42,10 +44,15 @@ require_relative "docker_swarm/middleware/response_json_parser"
42
44
  require_relative "docker_swarm/middleware/error_handler"
43
45
  require_relative "docker_swarm/connection"
44
46
  require_relative "docker_swarm/api"
45
- require_relative "docker_swarm/base"
47
+
48
+ # Concerns deben cargarse antes que Base si Base los incluye
46
49
  require_relative "docker_swarm/concerns/creatable"
47
50
  require_relative "docker_swarm/concerns/updatable"
48
51
  require_relative "docker_swarm/concerns/deletable"
52
+ require_relative "docker_swarm/concerns/loggable"
53
+ require_relative "docker_swarm/concerns/inspectable"
54
+
55
+ require_relative "docker_swarm/base"
49
56
 
50
57
  # Models
51
58
  require_relative "docker_swarm/models/swarm"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docker-swarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel
@@ -95,10 +95,13 @@ files:
95
95
  - lib/docker_swarm/base.rb
96
96
  - lib/docker_swarm/concerns/creatable.rb
97
97
  - lib/docker_swarm/concerns/deletable.rb
98
+ - lib/docker_swarm/concerns/inspectable.rb
99
+ - lib/docker_swarm/concerns/loggable.rb
98
100
  - lib/docker_swarm/concerns/updatable.rb
99
101
  - lib/docker_swarm/configuration.rb
100
102
  - lib/docker_swarm/connection.rb
101
103
  - lib/docker_swarm/errors.rb
104
+ - lib/docker_swarm/log_helper.rb
102
105
  - lib/docker_swarm/middleware/error_handler.rb
103
106
  - lib/docker_swarm/middleware/request_encoder.rb
104
107
  - lib/docker_swarm/middleware/response_json_parser.rb