docker-swarm 0.1.0 → 0.3.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 +4 -4
- data/README.md +12 -17
- data/lib/docker-swarm.rb +3 -0
- data/lib/docker_swarm/base.rb +12 -1
- data/lib/docker_swarm/configuration.rb +21 -0
- data/lib/docker_swarm/connection.rb +108 -17
- data/lib/docker_swarm/errors.rb +28 -1
- data/lib/docker_swarm/middleware/error_handler.rb +45 -18
- data/lib/docker_swarm/middleware/request_encoder.rb +19 -3
- data/lib/docker_swarm/middleware/response_json_parser.rb +18 -6
- data/lib/docker_swarm/models/service.rb +0 -9
- data/lib/docker_swarm/models/volume.rb +2 -6
- data/lib/docker_swarm/version.rb +1 -1
- data/lib/docker_swarm.rb +28 -29
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b46f33638fb88f8c9fca4a85747f67e266525933e4b22d38bb47e2ce1556a552
|
|
4
|
+
data.tar.gz: b080b742324d91eb1570af605b05539da67284fb76631faae04583f2577edad3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17f03f903b19273e822feb4a389ff9b862d034607f032c3b6112838b1ddf5f38927dabad4001214f6ad9ce6a06f8661939b38fb2fa3dda8a0b1975246da199fa
|
|
7
|
+
data.tar.gz: 44edbe59ffa431b2cf78735ca138c2fd4c41d0d57dda543509d9de8a417343c525d1a3493d549fa4e2ebfc63948491a2994917df493683c25f8c33e2f795eadf
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/rb/docker-swarm)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
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.
|
|
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
8
|
## 🚀 Inicio Rápido
|
|
9
9
|
|
|
@@ -13,42 +13,37 @@ require 'docker_swarm'
|
|
|
13
13
|
# Configurar (opcional, usa defaults)
|
|
14
14
|
DockerSwarm.configure do |config|
|
|
15
15
|
config.socket_path = "unix:///var/run/docker.sock"
|
|
16
|
+
config.log_level = Logger::INFO
|
|
17
|
+
config.read_timeout = 30
|
|
18
|
+
config.max_retries = 3
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
# Listar servicios
|
|
21
|
+
# Listar servicios (con soporte para Indifferent Access en arrays)
|
|
19
22
|
services = DockerSwarm::Service.all
|
|
20
|
-
services.each { |s| puts "#{s.ID}: #{s.Spec[
|
|
23
|
+
services.each { |s| puts "#{s.ID}: #{s.Spec[:Name]}" }
|
|
21
24
|
|
|
22
25
|
# Crear un nuevo servicio
|
|
23
26
|
service = DockerSwarm::Service.create(
|
|
27
|
+
Name: "my-webapp",
|
|
24
28
|
Spec: {
|
|
25
|
-
Name: "my-webapp",
|
|
26
29
|
TaskTemplate: {
|
|
27
30
|
ContainerSpec: { Image: "nginx:latest" }
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
)
|
|
31
34
|
|
|
32
|
-
# Obtener logs
|
|
35
|
+
# Obtener logs (stdout y stderr habilitados por defecto)
|
|
33
36
|
puts service.logs
|
|
34
37
|
```
|
|
35
38
|
|
|
36
|
-
## 📖 Documentación Completa
|
|
37
|
-
|
|
38
|
-
Para profundizar en el uso de la gema, consulta las siguientes guías:
|
|
39
|
-
|
|
40
|
-
1. **[Guía de Configuración](docs/configuration.md)**: Cómo configurar el socket, logger y opciones globales.
|
|
41
|
-
2. **[Uso del ORM (Modelos)](docs/models.md)**: Todo sobre el ciclo de vida de los recursos (`Service`, `Node`, `Task`, etc.).
|
|
42
|
-
3. **[Cliente de API (Bajo Nivel)](docs/api.md)**: Cómo realizar peticiones personalizadas directamente a la API de Docker.
|
|
43
|
-
4. **[Manejo de Errores](docs/errors.md)**: Jerarquía de excepciones y mapeo de errores de Docker.
|
|
44
|
-
5. **[Pruebas y Mocking](docs/testing.md)**: Guía para testear tu aplicación sin depender de un socket de Docker real.
|
|
45
|
-
|
|
46
39
|
## 🛠 Características Clave
|
|
47
40
|
|
|
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.
|
|
48
45
|
- **Mapeo PascalCase**: Mantiene la fidelidad con los atributos de Docker (e.g., `s.ID`, `s.Spec`) evitando transformaciones costosas.
|
|
49
46
|
- **ActiveModel Ready**: Soporta validaciones, serialización JSON y comportamientos estándar de modelos Ruby.
|
|
50
|
-
- **Surgical Updates**: Actualizaciones precisas enviando solo el índice de versión y el payload necesario.
|
|
51
|
-
- **Excon Stack**: Basado en `Excon` con middlewares para encoding de peticiones, parseo de respuestas y gestión de errores.
|
|
52
47
|
|
|
53
48
|
## 🤝 Contribuir
|
|
54
49
|
|
data/lib/docker-swarm.rb
ADDED
data/lib/docker_swarm/base.rb
CHANGED
|
@@ -13,8 +13,19 @@ module DockerSwarm
|
|
|
13
13
|
Api::ENDPOINTS[resource_name.to_sym]
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Key in the JSON response that contains the array of items.
|
|
17
|
+
# Override in subclasses if the API returns a wrapped object (e.g., Volumes).
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
def root_key
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
def all(filters = {})
|
|
17
|
-
_fetch_all(filters)
|
|
24
|
+
response = _fetch_all(filters)
|
|
25
|
+
return [] if response.blank?
|
|
26
|
+
|
|
27
|
+
data = root_key && response.is_a?(Hash) ? response[root_key] : response
|
|
28
|
+
Array(data).map { |item| new(item) }
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
def find(id)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module DockerSwarm
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :socket_path, :logger, :log_level,
|
|
8
|
+
:read_timeout, :write_timeout, :connect_timeout,
|
|
9
|
+
:max_retries
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@socket_path = "unix:///var/run/docker.sock"
|
|
13
|
+
@logger = Logger.new($stdout)
|
|
14
|
+
@log_level = Logger::INFO
|
|
15
|
+
@read_timeout = 60
|
|
16
|
+
@write_timeout = 60
|
|
17
|
+
@connect_timeout = 10
|
|
18
|
+
@max_retries = 3
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -10,29 +10,120 @@ module DockerSwarm
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def request(options = {})
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
14
|
+
|
|
15
|
+
log_event("request_started",
|
|
16
|
+
level: :debug,
|
|
17
|
+
data: { method: options[:method], path: options[:path] })
|
|
18
|
+
|
|
19
|
+
# Combinar opciones por defecto de la gema con las de la petición
|
|
20
|
+
request_options = {
|
|
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
|
|
27
|
+
}.merge(options)
|
|
28
|
+
|
|
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
|
+
|
|
39
|
+
response.body
|
|
15
40
|
rescue => e
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
# Excon suele envolver excepciones de middleware en Excon::Error::Socket.
|
|
42
|
+
# Intentamos recuperar la causa original si es una de nuestras excepciones.
|
|
43
|
+
actual_error = (e.respond_to?(:cause) && e.cause&.class&.name&.include?("DockerSwarm::Error")) ? e.cause : e
|
|
44
|
+
|
|
45
|
+
log_event("request_failure",
|
|
46
|
+
level: :error,
|
|
47
|
+
data: {
|
|
48
|
+
method: options[:method],
|
|
49
|
+
path: options[:path],
|
|
50
|
+
error: actual_error.class.name,
|
|
51
|
+
message: actual_error.message,
|
|
52
|
+
duration_ms: calculate_duration(start_time)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if actual_error.class.name.include?("DockerSwarm::Error")
|
|
56
|
+
raise actual_error
|
|
57
|
+
elsif actual_error.is_a?(Excon::Error::Socket)
|
|
58
|
+
raise ::DockerSwarm::Error::Communication, "Docker socket error: #{actual_error.message}"
|
|
18
59
|
else
|
|
19
|
-
raise
|
|
60
|
+
raise actual_error
|
|
20
61
|
end
|
|
21
62
|
end
|
|
63
|
+
|
|
22
64
|
private
|
|
23
65
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
def log_event(event, level: :info, data: {})
|
|
67
|
+
return unless logger
|
|
68
|
+
# Respetar el nivel de log antes de procesar nada
|
|
69
|
+
return if level == :debug && logger.level > Logger::DEBUG
|
|
70
|
+
|
|
71
|
+
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
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if level == :debug
|
|
89
|
+
logger.debug(&log_block)
|
|
90
|
+
else
|
|
91
|
+
logger.send(level, log_block.call)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def calculate_duration(start_time)
|
|
96
|
+
((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def common_middlewares
|
|
100
|
+
# Usamos los middlewares por defecto de Excon para garantizar que todas las llaves internas
|
|
101
|
+
# (como :retries_remaining o :instrumentor_name) se inicialicen correctamente.
|
|
102
|
+
Excon.defaults[:middlewares] + [
|
|
103
|
+
Excon::Middleware::RedirectFollower,
|
|
104
|
+
Middleware::RequestEncoder,
|
|
105
|
+
Middleware::ResponseJSONParser,
|
|
106
|
+
Middleware::ErrorHandler
|
|
107
|
+
]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def client
|
|
111
|
+
debug_enabled = logger&.level == Logger::DEBUG
|
|
112
|
+
|
|
113
|
+
options = {
|
|
114
|
+
middlewares: common_middlewares,
|
|
115
|
+
logger: logger,
|
|
116
|
+
debug_request: debug_enabled,
|
|
117
|
+
debug_response: debug_enabled,
|
|
118
|
+
# Si debug_enabled es true, Excon usará su lógica interna de debug con el logger proporcionado
|
|
119
|
+
retry_limit: 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@client ||= if socket_path.start_with?("unix://")
|
|
123
|
+
Excon.new("unix:///", options.merge(socket: socket_path.sub(/^unix:\/\//, "")))
|
|
124
|
+
else
|
|
125
|
+
Excon.new(socket_path, options)
|
|
126
|
+
end
|
|
36
127
|
end
|
|
37
128
|
end
|
|
38
129
|
end
|
data/lib/docker_swarm/errors.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module DockerSwarm
|
|
4
4
|
class Error < StandardError; end
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
class Error
|
|
7
7
|
class BadRequest < Error; end
|
|
8
8
|
class Unauthorized < Error; end
|
|
9
9
|
class Forbidden < Error; end
|
|
@@ -19,4 +19,31 @@ module DockerSwarm
|
|
|
19
19
|
class TooManyRequests < Error; end
|
|
20
20
|
class Communication < Error; end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
# Aliases para permitir acceso directo DockerSwarm::Conflict
|
|
24
|
+
BadRequest = Error::BadRequest
|
|
25
|
+
Unauthorized = Error::Unauthorized
|
|
26
|
+
Forbidden = Error::Forbidden
|
|
27
|
+
NotFound = Error::NotFound
|
|
28
|
+
NotAcceptable = Error::NotAcceptable
|
|
29
|
+
RequestTimeout = Error::RequestTimeout
|
|
30
|
+
Conflict = Error::Conflict
|
|
31
|
+
UnprocessableEntity = Error::UnprocessableEntity
|
|
32
|
+
InternalServerError = Error::InternalServerError
|
|
33
|
+
BadGateway = Error::BadGateway
|
|
34
|
+
ServiceUnavailable = Error::ServiceUnavailable
|
|
35
|
+
GatewayTimeout = Error::GatewayTimeout
|
|
36
|
+
TooManyRequests = Error::TooManyRequests
|
|
37
|
+
Communication = Error::Communication
|
|
38
|
+
|
|
39
|
+
# Módulo Errors para compatibilidad adicional
|
|
40
|
+
module Errors
|
|
41
|
+
def self.const_missing(name)
|
|
42
|
+
if ::DockerSwarm::Error.const_defined?(name)
|
|
43
|
+
::DockerSwarm::Error.const_get(name)
|
|
44
|
+
else
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
22
49
|
end
|
|
@@ -9,31 +9,58 @@ module DockerSwarm
|
|
|
9
9
|
status = env[:response][:status]
|
|
10
10
|
body = env[:response][:body]
|
|
11
11
|
|
|
12
|
+
return @stack.response_call(env) if (200..299).include?(status)
|
|
13
|
+
|
|
14
|
+
error_msg = error_message(body)
|
|
15
|
+
log_business_error(env, status, error_msg)
|
|
16
|
+
|
|
12
17
|
case status
|
|
13
|
-
when
|
|
14
|
-
|
|
15
|
-
when
|
|
16
|
-
when
|
|
17
|
-
when
|
|
18
|
-
when
|
|
19
|
-
when
|
|
20
|
-
when
|
|
21
|
-
when
|
|
22
|
-
when
|
|
23
|
-
when
|
|
24
|
-
when
|
|
25
|
-
when
|
|
26
|
-
when 503 then raise Errors::ServiceUnavailable, error_message(body)
|
|
27
|
-
when 504 then raise Errors::GatewayTimeout, error_message(body)
|
|
18
|
+
when 400 then raise ::DockerSwarm::BadRequest, error_msg
|
|
19
|
+
when 401 then raise ::DockerSwarm::Unauthorized, error_msg
|
|
20
|
+
when 403 then raise ::DockerSwarm::Forbidden, error_msg
|
|
21
|
+
when 404 then raise ::DockerSwarm::NotFound, error_msg
|
|
22
|
+
when 406 then raise ::DockerSwarm::NotAcceptable, error_msg
|
|
23
|
+
when 408 then raise ::DockerSwarm::RequestTimeout, error_msg
|
|
24
|
+
when 409 then raise ::DockerSwarm::Conflict, error_msg
|
|
25
|
+
when 422 then raise ::DockerSwarm::UnprocessableEntity, body
|
|
26
|
+
when 429 then raise ::DockerSwarm::TooManyRequests, error_msg
|
|
27
|
+
when 500 then raise ::DockerSwarm::InternalServerError, error_msg
|
|
28
|
+
when 502 then raise ::DockerSwarm::BadGateway, error_msg
|
|
29
|
+
when 503 then raise ::DockerSwarm::ServiceUnavailable, error_msg
|
|
30
|
+
when 504 then raise ::DockerSwarm::GatewayTimeout, error_msg
|
|
28
31
|
else
|
|
29
|
-
raise
|
|
32
|
+
raise ::DockerSwarm::Error, "HTTP #{status}: #{error_msg}"
|
|
30
33
|
end
|
|
31
|
-
|
|
32
|
-
@stack.response_call(env)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
private
|
|
36
37
|
|
|
38
|
+
def log_business_error(env, status, message)
|
|
39
|
+
logger = env[:logger]
|
|
40
|
+
return unless logger
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
payload = {
|
|
44
|
+
component: "docker_swarm.middleware.error_handler",
|
|
45
|
+
event: "business_error",
|
|
46
|
+
source: "http",
|
|
47
|
+
status: status,
|
|
48
|
+
message: message,
|
|
49
|
+
method: env[:method],
|
|
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(" ")
|
|
57
|
+
|
|
58
|
+
logger.error(kv_string)
|
|
59
|
+
rescue
|
|
60
|
+
# Resilience: logging should not crash the app
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
37
64
|
def error_message(body)
|
|
38
65
|
if body.is_a?(Hash)
|
|
39
66
|
body["message"] || body["error"] || body.to_json
|
|
@@ -4,12 +4,28 @@ module DockerSwarm
|
|
|
4
4
|
module Middleware
|
|
5
5
|
class RequestEncoder < Excon::Middleware::Base
|
|
6
6
|
def request_call(env)
|
|
7
|
-
if env[:body] && env[:body].is_a?(
|
|
8
|
-
env[:
|
|
9
|
-
env[:
|
|
7
|
+
if env[:body] && !env[:body].is_a?(String)
|
|
8
|
+
content_type = (env[:headers]["Content-Type"] || env[:headers][:content_type]).to_s
|
|
9
|
+
env[:body] = serialize_body(env[:body], content_type)
|
|
10
|
+
|
|
11
|
+
if content_type.blank? || content_type.include?("application/json")
|
|
12
|
+
env[:headers]["Content-Type"] ||= "application/json"
|
|
13
|
+
end
|
|
10
14
|
end
|
|
11
15
|
@stack.request_call(env)
|
|
12
16
|
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def serialize_body(body, content_type)
|
|
21
|
+
if content_type.include?("application/x-www-form-urlencoded")
|
|
22
|
+
URI.encode_www_form(body)
|
|
23
|
+
elsif content_type.include?("multipart/form-data")
|
|
24
|
+
body
|
|
25
|
+
else
|
|
26
|
+
body.to_json
|
|
27
|
+
end
|
|
28
|
+
end
|
|
13
29
|
end
|
|
14
30
|
end
|
|
15
31
|
end
|
|
@@ -9,17 +9,29 @@ module DockerSwarm
|
|
|
9
9
|
headers = env[:response][:headers] || {}
|
|
10
10
|
|
|
11
11
|
if body && !body.empty? && headers["Content-Type"]&.include?("application/json")
|
|
12
|
-
|
|
13
|
-
parsed = JSON.parse(body)
|
|
14
|
-
env[:response][:body] = parsed.is_a?(Hash) ? parsed.with_indifferent_access : parsed
|
|
15
|
-
rescue JSON::ParserError
|
|
16
|
-
# Keep original body if parsing fails
|
|
17
|
-
end
|
|
12
|
+
env[:response][:body] = parse_json(body)
|
|
18
13
|
end
|
|
19
14
|
end
|
|
20
15
|
|
|
21
16
|
@stack.response_call(env)
|
|
22
17
|
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def parse_json(body)
|
|
22
|
+
result = body.is_a?(String) ? JSON.parse(body) : body
|
|
23
|
+
|
|
24
|
+
case result
|
|
25
|
+
when Hash
|
|
26
|
+
result.with_indifferent_access
|
|
27
|
+
when Array
|
|
28
|
+
result.map { |item| item.is_a?(Hash) ? item.with_indifferent_access : item }
|
|
29
|
+
else
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
rescue JSON::ParserError
|
|
33
|
+
body
|
|
34
|
+
end
|
|
23
35
|
end
|
|
24
36
|
end
|
|
25
37
|
end
|
|
@@ -8,20 +8,11 @@ module DockerSwarm
|
|
|
8
8
|
include Concerns::Updatable
|
|
9
9
|
include Concerns::Deletable
|
|
10
10
|
|
|
11
|
-
validate :validate_name_presence
|
|
12
|
-
|
|
13
11
|
# Fetches logs for the service
|
|
14
12
|
# @param query_params [Hash] Query parameters for the logs endpoint (stdout, stderr, follow, etc.)
|
|
15
13
|
# @return [String] The raw log stream
|
|
16
14
|
def logs(query_params = { stdout: 1, stderr: 1 })
|
|
17
15
|
Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
|
|
18
16
|
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def validate_name_presence
|
|
23
|
-
name = attributes["Name"] || (respond_to?(:Name) ? Name : nil)
|
|
24
|
-
errors.add(:Name, "can't be blank") if name.blank?
|
|
25
|
-
end
|
|
26
17
|
end
|
|
27
18
|
end
|
|
@@ -5,12 +5,8 @@ module DockerSwarm
|
|
|
5
5
|
include Concerns::Creatable
|
|
6
6
|
include Concerns::Deletable
|
|
7
7
|
|
|
8
|
-
def self.
|
|
9
|
-
|
|
10
|
-
return [] if response.blank?
|
|
11
|
-
|
|
12
|
-
data = response.is_a?(Hash) ? response["Volumes"] : response
|
|
13
|
-
Array(data).map { |item| new(item) }
|
|
8
|
+
def self.root_key
|
|
9
|
+
"Volumes"
|
|
14
10
|
end
|
|
15
11
|
end
|
|
16
12
|
end
|
data/lib/docker_swarm/version.rb
CHANGED
data/lib/docker_swarm.rb
CHANGED
|
@@ -7,6 +7,34 @@ require "excon"
|
|
|
7
7
|
require "json"
|
|
8
8
|
require "logger"
|
|
9
9
|
|
|
10
|
+
module DockerSwarm
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :configuration
|
|
13
|
+
|
|
14
|
+
def configure
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
yield(@configuration) if block_given?
|
|
17
|
+
|
|
18
|
+
if @configuration.logger.respond_to?(:level=)
|
|
19
|
+
@configuration.logger.level = @configuration.log_level
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@connection = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def connection
|
|
26
|
+
configure unless @configuration
|
|
27
|
+
@connection ||= Connection.new(@configuration.socket_path, @configuration.logger)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def request(options = {})
|
|
31
|
+
connection.request(options)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Primero cargamos la configuración porque la clase DockerSwarm la usa arriba
|
|
37
|
+
require_relative "docker_swarm/configuration"
|
|
10
38
|
require_relative "docker_swarm/version"
|
|
11
39
|
require_relative "docker_swarm/errors"
|
|
12
40
|
require_relative "docker_swarm/middleware/request_encoder"
|
|
@@ -31,32 +59,3 @@ require_relative "docker_swarm/models/network"
|
|
|
31
59
|
require_relative "docker_swarm/models/config"
|
|
32
60
|
require_relative "docker_swarm/models/secret"
|
|
33
61
|
require_relative "docker_swarm/models/volume"
|
|
34
|
-
|
|
35
|
-
module DockerSwarm
|
|
36
|
-
class << self
|
|
37
|
-
attr_accessor :configuration
|
|
38
|
-
|
|
39
|
-
def configure
|
|
40
|
-
self.configuration ||= Configuration.new
|
|
41
|
-
yield(configuration) if block_given?
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def connection
|
|
45
|
-
configure unless configuration
|
|
46
|
-
@connection ||= Connection.new(configuration.socket_path, configuration.logger)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def request(options = {})
|
|
50
|
-
connection.request(options)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
class Configuration
|
|
55
|
-
attr_accessor :socket_path, :logger
|
|
56
|
-
|
|
57
|
-
def initialize
|
|
58
|
-
@socket_path = "unix:///var/run/docker.sock"
|
|
59
|
-
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel
|
|
@@ -89,12 +89,14 @@ extensions: []
|
|
|
89
89
|
extra_rdoc_files: []
|
|
90
90
|
files:
|
|
91
91
|
- README.md
|
|
92
|
+
- lib/docker-swarm.rb
|
|
92
93
|
- lib/docker_swarm.rb
|
|
93
94
|
- lib/docker_swarm/api.rb
|
|
94
95
|
- lib/docker_swarm/base.rb
|
|
95
96
|
- lib/docker_swarm/concerns/creatable.rb
|
|
96
97
|
- lib/docker_swarm/concerns/deletable.rb
|
|
97
98
|
- lib/docker_swarm/concerns/updatable.rb
|
|
99
|
+
- lib/docker_swarm/configuration.rb
|
|
98
100
|
- lib/docker_swarm/connection.rb
|
|
99
101
|
- lib/docker_swarm/errors.rb
|
|
100
102
|
- lib/docker_swarm/middleware/error_handler.rb
|