docker-swarm 0.4.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 +4 -4
- data/README.md +105 -24
- data/lib/docker_swarm/log_helper.rb +1 -1
- data/lib/docker_swarm/version.rb +1 -1
- data/skill/SKILL.md +227 -0
- data/skill/references/api-detallada.md +246 -0
- data/skill/references/errores.md +157 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5c66a1153f6aa54787f22d1281e4b8d1932a951f70000ff0a3af72e79c9be7b
|
|
4
|
+
data.tar.gz: 6469d6833f97509511d0bfffda44bbbdc650fa1c928d624efef79b5ba71221ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
##
|
|
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
|
|
17
|
-
config.read_timeout = 30
|
|
30
|
+
config.log_level = Logger::INFO
|
|
18
31
|
config.max_retries = 3
|
|
19
32
|
end
|
|
20
33
|
|
|
21
|
-
#
|
|
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
|
-
|
|
44
|
+
### Crear un servicio
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
26
47
|
service = DockerSwarm::Service.create(
|
|
27
48
|
Name: "my-webapp",
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
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
|
-
##
|
|
128
|
+
## Contribuir
|
|
49
129
|
|
|
50
|
-
Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las
|
|
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
|
-
##
|
|
137
|
+
## Licencia
|
|
57
138
|
|
|
58
|
-
Este proyecto
|
|
139
|
+
Este proyecto esta bajo la licencia MIT.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module DockerSwarm
|
|
4
4
|
# Helper module to centralize logging logic and formatting
|
|
5
5
|
module LogHelper
|
|
6
|
-
SENSITIVE_KEYS = /password|token|api_key|auth|secret/i.freeze
|
|
6
|
+
SENSITIVE_KEYS = /password|token|api_key|auth|secret|data/i.freeze
|
|
7
7
|
|
|
8
8
|
# Formats a hash into a KV structured string with sensitive data masking
|
|
9
9
|
# @param payload [Hash] The data to format
|
data/lib/docker_swarm/version.rb
CHANGED
data/skill/SKILL.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# DockerSwarm Expert
|
|
2
|
+
|
|
3
|
+
Skill de conocimiento completo sobre DockerSwarm. Consultame para cualquier pregunta sobre integración, arquitectura, API, errores y antipatrones.
|
|
4
|
+
|
|
5
|
+
## Glosario
|
|
6
|
+
|
|
7
|
+
**Base** — Clase ORM base que hereda de ActiveModel::Model. Provee accessors dinámicos PascalCase, `find`, `all`, `where`, `reload`, `payload_for_docker`. Todos los modelos heredan de ella.
|
|
8
|
+
|
|
9
|
+
**Concern** — Mixin ActiveSupport::Concern que agrega comportamiento CRUD a un modelo: Creatable (POST), Updatable (POST con Version.Index), Deletable (DELETE), Loggable (logs streaming), Inspectable (#inspect legible).
|
|
10
|
+
|
|
11
|
+
**Middleware** — Capa Excon que procesa request/response: RequestEncoder (serialización body), ResponseJSONParser (parsing + indifferent access), ErrorHandler (status HTTP → excepción).
|
|
12
|
+
|
|
13
|
+
**Deep Indifferent Access** — Toda respuesta JSON se convierte recursivamente a `HashWithIndifferentAccess`, permitiendo acceso por symbol o string en cualquier nivel de anidamiento.
|
|
14
|
+
|
|
15
|
+
**Dynamic Accessor** — Cuando Docker devuelve un atributo nuevo (ej: `Spec`, `Status`), Base crea `attr_accessor` dinámicamente via `method_missing`. Se cachea en `defined_attributes` (Set) para no redefinir.
|
|
16
|
+
|
|
17
|
+
**Version.Index** — Mecanismo de Docker para updates atómicos. Updatable extrae `Version["Index"]` y lo envía como query param para evitar race conditions.
|
|
18
|
+
|
|
19
|
+
**LogHelper** — Módulo que formatea logs en KV (`key=value`) y enmascara campos sensibles matching `/password|token|api_key|auth|secret|data/i` → `[FILTERED]`.
|
|
20
|
+
|
|
21
|
+
## Arquitectura
|
|
22
|
+
|
|
23
|
+
### Responsabilidad core
|
|
24
|
+
|
|
25
|
+
ORM ligero compatible con ActiveModel para Docker Engine API. Comunica via Excon sobre Unix socket (default) o TCP. Todos los requests pasan por una cadena de middlewares.
|
|
26
|
+
|
|
27
|
+
### Mapa de componentes
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
31
|
+
│ Modelo │────>│ Api │────>│ Connection │
|
|
32
|
+
│ (Service, │ │ (ENDPOINTS │ │ (Excon + │
|
|
33
|
+
│ Node...) │ │ + request) │ │ Timeouts) │
|
|
34
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌───────────────────────┐
|
|
38
|
+
│ Middleware Stack │
|
|
39
|
+
│ RequestEncoder │
|
|
40
|
+
│ ResponseJSONParser │
|
|
41
|
+
│ ErrorHandler │
|
|
42
|
+
└───────────────────────┘
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Flujo en runtime
|
|
46
|
+
|
|
47
|
+
1. Modelo invoca `Api.request(action:, arguments:, query_params:, payload:)`.
|
|
48
|
+
2. Api formatea el path con `format()` y delega a `DockerSwarm.request`.
|
|
49
|
+
3. Connection crea/reutiliza cliente Excon singleton con middlewares.
|
|
50
|
+
4. RequestEncoder serializa body (JSON default, form-urlencoded, multipart).
|
|
51
|
+
5. Excon envía al Docker daemon (socket o TCP).
|
|
52
|
+
6. ResponseJSONParser parsea JSON y aplica `with_indifferent_access` recursivo.
|
|
53
|
+
7. ErrorHandler mapea status 4xx/5xx a excepciones tipadas.
|
|
54
|
+
8. Connection mide duración con `CLOCK_MONOTONIC` y loguea en KV.
|
|
55
|
+
|
|
56
|
+
### Decisiones de diseño
|
|
57
|
+
|
|
58
|
+
- **PascalCase fiel**: Los atributos mantienen el naming de Docker (`Spec`, `TaskTemplate`, `ContainerSpec`). No se transforman a snake_case.
|
|
59
|
+
- **Spec merging**: `assign_attributes` hace `deep_merge` cuando el atributo es `Spec` para no perder campos anidados en updates.
|
|
60
|
+
- **Singleton connection**: `@client ||= Excon.new(...)` — se reutiliza, se resetea al cambiar configuración.
|
|
61
|
+
- **Retries en Excon**: `idempotent: true` + `retry_errors: [Socket, Timeout]` con `max_retries` configurable.
|
|
62
|
+
|
|
63
|
+
## API Pública (resumen)
|
|
64
|
+
|
|
65
|
+
### Configuración
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
DockerSwarm.configure do |config|
|
|
69
|
+
config.socket_path = "unix:///var/run/docker.sock" # o http://host:port
|
|
70
|
+
config.logger = Logger.new($stdout)
|
|
71
|
+
config.log_level = Logger::INFO
|
|
72
|
+
config.read_timeout = 60.0 # segundos
|
|
73
|
+
config.write_timeout = 60.0
|
|
74
|
+
config.connect_timeout = 10.0
|
|
75
|
+
config.max_retries = 3
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Operaciones comunes
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Listar
|
|
83
|
+
DockerSwarm::Service.all
|
|
84
|
+
DockerSwarm::Service.all(label: ["app=web"])
|
|
85
|
+
|
|
86
|
+
# Buscar
|
|
87
|
+
service = DockerSwarm::Service.find("service_id") # nil si no existe
|
|
88
|
+
|
|
89
|
+
# Crear
|
|
90
|
+
svc = DockerSwarm::Service.create(Name: "web", TaskTemplate: { ... })
|
|
91
|
+
|
|
92
|
+
# Actualizar (maneja Version.Index automáticamente)
|
|
93
|
+
service.update(Spec: { Replicas: 3 })
|
|
94
|
+
|
|
95
|
+
# Eliminar (graceful con 404)
|
|
96
|
+
service.destroy
|
|
97
|
+
|
|
98
|
+
# Logs
|
|
99
|
+
service.logs(stdout: 1, stderr: 1)
|
|
100
|
+
|
|
101
|
+
# Sistema
|
|
102
|
+
DockerSwarm::System.up # ping
|
|
103
|
+
DockerSwarm::System.info # daemon info
|
|
104
|
+
DockerSwarm::System.version # versión Docker
|
|
105
|
+
DockerSwarm::System.df # disk usage
|
|
106
|
+
DockerSwarm::Swarm.show # info del cluster
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Ver [API Detallada](references/api-detallada.md) para la referencia completa de todos los modelos.
|
|
110
|
+
|
|
111
|
+
## FAQ
|
|
112
|
+
|
|
113
|
+
### Como conecto a un Docker remoto por TCP?
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
DockerSwarm.configure do |config|
|
|
117
|
+
config.socket_path = "http://192.168.1.100:2375"
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
Connection detecta si empieza con `unix://` (socket) o no (TCP) y configura Excon.
|
|
121
|
+
|
|
122
|
+
### Por qué los atributos son PascalCase y no snake_case?
|
|
123
|
+
|
|
124
|
+
Docker Engine API usa PascalCase en todos sus JSON. La gema mantiene fidelidad 1:1 para evitar confusión al leer la documentación de Docker. Se accede igual: `service.Spec`, `node.Status`.
|
|
125
|
+
|
|
126
|
+
### Como filtro recursos con labels?
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
DockerSwarm::Service.all(label: ["env=production", "app=web"])
|
|
130
|
+
DockerSwarm::Node.all(role: ["manager"])
|
|
131
|
+
```
|
|
132
|
+
Los filtros se serializan como JSON en el query param `filters` del Docker API.
|
|
133
|
+
|
|
134
|
+
### Como manejo errores de conexión?
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
begin
|
|
138
|
+
DockerSwarm::System.up
|
|
139
|
+
rescue DockerSwarm::Communication => e
|
|
140
|
+
# Socket caído o inalcanzable
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
`Communication` envuelve errores de `Excon::Error::Socket`. Los retries se aplican automáticamente (`max_retries`).
|
|
144
|
+
|
|
145
|
+
### Puedo usar validaciones de ActiveModel?
|
|
146
|
+
|
|
147
|
+
Sí. Todos los modelos heredan de `ActiveModel::Model`. Podés agregar validaciones custom:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
service = DockerSwarm::Service.new(Name: "")
|
|
151
|
+
service.valid? # usa validaciones ActiveModel
|
|
152
|
+
service.save # retorna false si invalid?
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Antipatrones
|
|
156
|
+
|
|
157
|
+
### Transformar atributos a snake_case
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# MAL
|
|
161
|
+
service.task_template["container_spec"]
|
|
162
|
+
|
|
163
|
+
# BIEN
|
|
164
|
+
service.TaskTemplate["ContainerSpec"]
|
|
165
|
+
```
|
|
166
|
+
**Razón:** Docker API usa PascalCase. La gema mantiene fidelidad. Con indifferent access, podés usar strings o symbols, pero siempre PascalCase.
|
|
167
|
+
|
|
168
|
+
### Reemplazar Spec en vez de mergear
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
# MAL — pierde campos existentes del Spec
|
|
172
|
+
service.update(Spec: { Mode: { Replicated: { Replicas: 5 } } })
|
|
173
|
+
|
|
174
|
+
# BIEN — mergear solo lo que cambia
|
|
175
|
+
service.update(Mode: { Replicated: { Replicas: 5 } })
|
|
176
|
+
```
|
|
177
|
+
**Razón:** `payload_for_docker` extrae contenido de Spec al root. Si pasás Spec completo, `assign_attributes` hace deep_merge, pero es más limpio pasar los campos directamente.
|
|
178
|
+
|
|
179
|
+
### Ignorar Version.Index en updates
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# MAL — update manual sin version
|
|
183
|
+
DockerSwarm.request(method: :post, path: "services/#{id}/update", body: payload)
|
|
184
|
+
|
|
185
|
+
# BIEN — usar el modelo, maneja version automáticamente
|
|
186
|
+
service.update(new_attrs)
|
|
187
|
+
```
|
|
188
|
+
**Razón:** Docker requiere `version` query param para updates atómicos. Updatable lo extrae de `self.Version["Index"]` automáticamente.
|
|
189
|
+
|
|
190
|
+
### No capturar errores específicos
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
# MAL
|
|
194
|
+
begin
|
|
195
|
+
service.destroy
|
|
196
|
+
rescue StandardError
|
|
197
|
+
# tragarse todo
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# BIEN
|
|
201
|
+
begin
|
|
202
|
+
service.destroy
|
|
203
|
+
rescue DockerSwarm::NotFound
|
|
204
|
+
# ya fue eliminado, ok
|
|
205
|
+
rescue DockerSwarm::Conflict => e
|
|
206
|
+
# servicio en uso
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
**Razón:** La jerarquía de errores mapea cada status HTTP. Capturar errores específicos permite manejar cada caso.
|
|
210
|
+
|
|
211
|
+
## Errores
|
|
212
|
+
|
|
213
|
+
Los errores más comunes. Ver [Catálogo de Errores](references/errores.md) para la referencia completa.
|
|
214
|
+
|
|
215
|
+
| Excepción | Status | Causa típica |
|
|
216
|
+
|-----------|--------|--------------|
|
|
217
|
+
| `NotFound` | 404 | Recurso eliminado o ID incorrecto |
|
|
218
|
+
| `Conflict` | 409 | Nombre duplicado o recurso en uso |
|
|
219
|
+
| `Communication` | — | Socket caído o inalcanzable |
|
|
220
|
+
| `ServiceUnavailable` | 503 | Docker daemon reiniciando |
|
|
221
|
+
|
|
222
|
+
Todas heredan de `DockerSwarm::Error`. Se acceden como `DockerSwarm::NotFound` (alias) o `DockerSwarm::Error::NotFound`.
|
|
223
|
+
|
|
224
|
+
## Referencias
|
|
225
|
+
|
|
226
|
+
- [API Detallada](references/api-detallada.md) — Referencia completa de modelos, concerns y métodos
|
|
227
|
+
- [Catálogo de Errores](references/errores.md) — Todas las excepciones con causa y resolución
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# API Detallada
|
|
2
|
+
|
|
3
|
+
Referencia completa de modelos, concerns, middleware y métodos públicos de DockerSwarm.
|
|
4
|
+
|
|
5
|
+
## Modelos — Tabla de capacidades
|
|
6
|
+
|
|
7
|
+
| Modelo | Creatable | Updatable | Deletable | Loggable | Extra |
|
|
8
|
+
|--------|-----------|-----------|-----------|----------|-------|
|
|
9
|
+
| Service | x | x | x | x | Ciclo de vida completo |
|
|
10
|
+
| Node | | x | x | | Miembros del cluster, no se crean |
|
|
11
|
+
| Task | | | | x | Read-only, generados por services |
|
|
12
|
+
| Container | | | x | x | `#start`, `#stop` |
|
|
13
|
+
| Network | x | x | x | | CRUD completo |
|
|
14
|
+
| Volume | x | | x | | `root_key = "Volumes"` |
|
|
15
|
+
| Config | x | | x | | Configuración del cluster |
|
|
16
|
+
| Secret | x | | x | | Datos sensibles |
|
|
17
|
+
| Image | x | | x | | Pull/removal |
|
|
18
|
+
| Swarm | | | | | `.show` (estático) |
|
|
19
|
+
| System | | | | | `.info`, `.version`, `.up`, `.df` |
|
|
20
|
+
|
|
21
|
+
## Base — Métodos de clase
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# Listar todos (con filtros opcionales)
|
|
25
|
+
# @param filters [Hash] Filtros Docker (label:, name:, id:, role:, etc.)
|
|
26
|
+
# @return [Array<Model>]
|
|
27
|
+
Model.all(filters = {})
|
|
28
|
+
|
|
29
|
+
# Alias de .all
|
|
30
|
+
Model.where(filters)
|
|
31
|
+
|
|
32
|
+
# Buscar por ID (retorna nil si 404)
|
|
33
|
+
# @param id [String]
|
|
34
|
+
# @return [Model, nil]
|
|
35
|
+
Model.find(id)
|
|
36
|
+
|
|
37
|
+
# Nombre del recurso pluralizado (ej: "services", "nodes")
|
|
38
|
+
Model.resource_name
|
|
39
|
+
|
|
40
|
+
# Endpoints del modelo desde Api::ENDPOINTS
|
|
41
|
+
Model.routes
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Filtros soportados
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# Filtros Docker se serializan como JSON en query param `filters`
|
|
48
|
+
DockerSwarm::Service.all(label: ["app=web"], name: ["my-service"])
|
|
49
|
+
DockerSwarm::Container.all(status: ["running"])
|
|
50
|
+
DockerSwarm::Node.all(role: ["manager"])
|
|
51
|
+
|
|
52
|
+
# Parámetros globales (no van en filters)
|
|
53
|
+
DockerSwarm::Image.all(all: true) # incluir intermedias
|
|
54
|
+
DockerSwarm::Container.all(limit: 10) # limitar resultados
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Base — Métodos de instancia
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# ID del recurso
|
|
61
|
+
# @return [String]
|
|
62
|
+
model.id
|
|
63
|
+
|
|
64
|
+
# Hash de atributos (excluye internos de ActiveModel)
|
|
65
|
+
# @return [Hash]
|
|
66
|
+
model.attributes
|
|
67
|
+
|
|
68
|
+
# Recarga desde Docker API
|
|
69
|
+
# @return [self]
|
|
70
|
+
model.reload
|
|
71
|
+
|
|
72
|
+
# Prepara payload para Docker (excluye ID, Version, CreatedAt, extrae Spec)
|
|
73
|
+
# @return [Hash]
|
|
74
|
+
model.payload_for_docker
|
|
75
|
+
|
|
76
|
+
# Persistido? (tiene ID)
|
|
77
|
+
# @return [Boolean]
|
|
78
|
+
model.persisted?
|
|
79
|
+
|
|
80
|
+
# Inspección legible: #<DockerSwarm::Service ID: abc, Name: web, Image: nginx>
|
|
81
|
+
model.inspect
|
|
82
|
+
|
|
83
|
+
# Serialización
|
|
84
|
+
model.as_json
|
|
85
|
+
model.serializable_hash
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Concern: Creatable
|
|
89
|
+
|
|
90
|
+
Incluido en: Service, Network, Volume, Config, Secret, Image.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Crear y persistir
|
|
94
|
+
# @param attributes [Hash] Atributos PascalCase
|
|
95
|
+
# @return [Model] instancia (con ID si exitoso)
|
|
96
|
+
Model.create(attributes)
|
|
97
|
+
|
|
98
|
+
# Persistir instancia nueva (o delegar a update si persisted?)
|
|
99
|
+
# @return [Boolean] false si validación falla
|
|
100
|
+
model.save
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Flujo interno de `save`: `valid?` → `Api.request(:create, payload_for_docker)` → asigna ID de response → `reload`.
|
|
104
|
+
|
|
105
|
+
## Concern: Updatable
|
|
106
|
+
|
|
107
|
+
Incluido en: Service, Node, Network.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# Actualizar recurso persistido
|
|
111
|
+
# @param new_attributes [Hash] Atributos a mergear
|
|
112
|
+
# @return [Boolean] false si validación falla
|
|
113
|
+
model.update(new_attributes = {})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Flujo interno: `assign_attributes` (deep_merge en Spec) → `valid?` → `Api.request(:update, id:, version: Version["Index"], payload:)`.
|
|
117
|
+
|
|
118
|
+
**Importante:** El query param `version` se extrae automáticamente de `self.Version["Index"]`. Sin esto, Docker rechaza el update con 500.
|
|
119
|
+
|
|
120
|
+
## Concern: Deletable
|
|
121
|
+
|
|
122
|
+
Incluido en: Service, Node, Container, Network, Volume, Config, Secret, Image.
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# Eliminar por instancia
|
|
126
|
+
# @return [true, nil] nil si ya no existía (404 graceful)
|
|
127
|
+
model.destroy
|
|
128
|
+
|
|
129
|
+
# Eliminar por ID (class method)
|
|
130
|
+
# @return [true, nil]
|
|
131
|
+
Model.destroy(id)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Concern: Loggable
|
|
135
|
+
|
|
136
|
+
Incluido en: Service, Task, Container.
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
# Obtener logs del recurso
|
|
140
|
+
# @param query_params [Hash] stdout:, stderr:, follow:, tail:, since:, timestamps:
|
|
141
|
+
# @return [String] Raw log stream
|
|
142
|
+
model.logs(query_params = { stdout: 1, stderr: 1 })
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Container — Métodos específicos
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
container = DockerSwarm::Container.find("container_id")
|
|
149
|
+
|
|
150
|
+
# Iniciar contenedor detenido
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
container.start
|
|
153
|
+
|
|
154
|
+
# Detener contenedor en ejecución
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
container.stop
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## System — Métodos estáticos
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
# Ping al daemon
|
|
163
|
+
# @return [String] "OK"
|
|
164
|
+
DockerSwarm::System.up
|
|
165
|
+
|
|
166
|
+
# Información del daemon
|
|
167
|
+
# @return [Hash] (Containers, Images, Driver, MemoryLimit, etc.)
|
|
168
|
+
DockerSwarm::System.info
|
|
169
|
+
|
|
170
|
+
# Versión de Docker
|
|
171
|
+
# @return [Hash] (Version, ApiVersion, Os, Arch, etc.)
|
|
172
|
+
DockerSwarm::System.version
|
|
173
|
+
|
|
174
|
+
# Uso de disco
|
|
175
|
+
# @return [Hash] (LayersSize, Images, Containers, Volumes)
|
|
176
|
+
DockerSwarm::System.df
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Swarm — Método estático
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# Información del cluster Swarm
|
|
183
|
+
# @return [Hash] (ID, Version, Spec, JoinTokens, etc.)
|
|
184
|
+
DockerSwarm::Swarm.show
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Volume — Particularidad
|
|
188
|
+
|
|
189
|
+
Volume sobreescribe `root_key` porque Docker envuelve la respuesta en `{"Volumes": [...]}`:
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
class Volume < Base
|
|
193
|
+
def self.root_key = "Volumes"
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Api — Bajo nivel
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# Request directo al Docker API
|
|
201
|
+
# @param action [Hash] {method:, path:} desde ENDPOINTS
|
|
202
|
+
# @param arguments [Hash] Interpolación en path (id:)
|
|
203
|
+
# @param query_params [Hash] Query string
|
|
204
|
+
# @param payload [Hash, nil] Body del request
|
|
205
|
+
DockerSwarm::Api.request(action:, arguments: {}, query_params: {}, payload: nil)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Endpoints registrados
|
|
209
|
+
|
|
210
|
+
Todos definidos en `Api::ENDPOINTS` como Hash frozen:
|
|
211
|
+
|
|
212
|
+
| Recurso | Acciones |
|
|
213
|
+
|---------|----------|
|
|
214
|
+
| swarm | show |
|
|
215
|
+
| system | info, version, up, df |
|
|
216
|
+
| nodes | index, show, update, destroy |
|
|
217
|
+
| tasks | index, show, logs |
|
|
218
|
+
| services | index, show, create, update, destroy, logs |
|
|
219
|
+
| configs | index, show, create, destroy |
|
|
220
|
+
| secrets | index, show, create, destroy |
|
|
221
|
+
| networks | index, show, create, update, destroy |
|
|
222
|
+
| volumes | index, show, create, destroy |
|
|
223
|
+
| containers | index, show, create, start, stop, destroy, logs |
|
|
224
|
+
| images | index, show, create, destroy |
|
|
225
|
+
|
|
226
|
+
## Middleware Stack
|
|
227
|
+
|
|
228
|
+
Orden de ejecución en Excon:
|
|
229
|
+
|
|
230
|
+
1. **Excon defaults** (Retry, Instrumentor, etc.)
|
|
231
|
+
2. **Excon::Middleware::RedirectFollower**
|
|
232
|
+
3. **RequestEncoder** — Serializa body: JSON (default), form-urlencoded, multipart. Detecta por Content-Type header.
|
|
233
|
+
4. **ResponseJSONParser** — Parsea JSON si Content-Type incluye `application/json`. Aplica `with_indifferent_access` recursivo (Hash y Array de Hashes).
|
|
234
|
+
5. **ErrorHandler** — Status 4xx/5xx → excepción tipada. Loguea `business_error` antes de raise.
|
|
235
|
+
|
|
236
|
+
## Configuración
|
|
237
|
+
|
|
238
|
+
| Opción | Tipo | Default | Descripción |
|
|
239
|
+
|--------|------|---------|-------------|
|
|
240
|
+
| `socket_path` | String | `unix:///var/run/docker.sock` | Socket Unix o URL TCP |
|
|
241
|
+
| `logger` | Logger | `Logger.new($stdout)` | Logger para KV output |
|
|
242
|
+
| `log_level` | Integer | `Logger::INFO` | Nivel de log (se aplica al logger) |
|
|
243
|
+
| `read_timeout` | Float | `60.0` | Timeout lectura (segundos) |
|
|
244
|
+
| `write_timeout` | Float | `60.0` | Timeout escritura (segundos) |
|
|
245
|
+
| `connect_timeout` | Float | `10.0` | Timeout conexión (segundos) |
|
|
246
|
+
| `max_retries` | Integer | `3` | Reintentos en Socket/Timeout errors |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Catálogo de Errores
|
|
2
|
+
|
|
3
|
+
Referencia completa de excepciones de DockerSwarm. Todas heredan de `DockerSwarm::Error`.
|
|
4
|
+
|
|
5
|
+
## Jerarquía
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
DockerSwarm::Error (base)
|
|
9
|
+
├── BadRequest (400)
|
|
10
|
+
├── Unauthorized (401)
|
|
11
|
+
├── Forbidden (403)
|
|
12
|
+
├── NotFound (404)
|
|
13
|
+
├── NotAcceptable (406)
|
|
14
|
+
├── RequestTimeout (408)
|
|
15
|
+
├── Conflict (409)
|
|
16
|
+
├── UnprocessableEntity (422)
|
|
17
|
+
├── TooManyRequests (429)
|
|
18
|
+
├── InternalServerError (500)
|
|
19
|
+
├── BadGateway (502)
|
|
20
|
+
├── ServiceUnavailable (503)
|
|
21
|
+
├── GatewayTimeout (504)
|
|
22
|
+
└── Communication (socket/red)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Acceso
|
|
26
|
+
|
|
27
|
+
Cada excepción tiene 3 formas de acceso equivalentes:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
DockerSwarm::NotFound # alias directo (recomendado)
|
|
31
|
+
DockerSwarm::Error::NotFound # acceso via clase Error
|
|
32
|
+
DockerSwarm::Errors::NotFound # módulo Errors (const_missing dinámico)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Catálogo completo
|
|
36
|
+
|
|
37
|
+
### BadRequest (400)
|
|
38
|
+
|
|
39
|
+
**Causa:** Payload malformado o parámetros inválidos.
|
|
40
|
+
**Reproducción:** Enviar JSON con campos incorrectos (ej: `Replicas: "abc"` en vez de integer).
|
|
41
|
+
**Resolución:** Validar payload contra la documentación de Docker API. Verificar tipos de datos.
|
|
42
|
+
|
|
43
|
+
### Unauthorized (401)
|
|
44
|
+
|
|
45
|
+
**Causa:** Credenciales inválidas o ausentes para Docker daemon con TLS.
|
|
46
|
+
**Reproducción:** Conectar a daemon protegido sin certificados.
|
|
47
|
+
**Resolución:** Configurar TLS client certificates en Excon o en la URL de conexión.
|
|
48
|
+
|
|
49
|
+
### Forbidden (403)
|
|
50
|
+
|
|
51
|
+
**Causa:** Permisos insuficientes para la operación.
|
|
52
|
+
**Reproducción:** Intentar operación de swarm en un nodo worker.
|
|
53
|
+
**Resolución:** Verificar que el nodo es manager y que el usuario tiene permisos sobre el socket.
|
|
54
|
+
|
|
55
|
+
### NotFound (404)
|
|
56
|
+
|
|
57
|
+
**Causa:** Recurso no existe o fue eliminado.
|
|
58
|
+
**Reproducción:** `Service.find("id_inexistente")`.
|
|
59
|
+
**Resolución:** `Base.find` retorna `nil` automáticamente. `Deletable#destroy` también es graceful (retorna `nil` en 404). No requiere rescue manual en estos casos.
|
|
60
|
+
|
|
61
|
+
### NotAcceptable (406)
|
|
62
|
+
|
|
63
|
+
**Causa:** El servidor no puede producir una respuesta aceptable.
|
|
64
|
+
**Reproducción:** Raro en Docker API.
|
|
65
|
+
**Resolución:** Verificar headers Accept del request.
|
|
66
|
+
|
|
67
|
+
### RequestTimeout (408)
|
|
68
|
+
|
|
69
|
+
**Causa:** Docker daemon tardó demasiado en responder.
|
|
70
|
+
**Reproducción:** Operación en un cluster sobrecargado.
|
|
71
|
+
**Resolución:** Incrementar `read_timeout` en configuración. Verificar estado del cluster.
|
|
72
|
+
|
|
73
|
+
### Conflict (409)
|
|
74
|
+
|
|
75
|
+
**Causa:** Nombre duplicado, recurso en uso, o versión desactualizada.
|
|
76
|
+
**Reproducción:** `Service.create(Name: "nombre_existente")` o update con `Version.Index` stale.
|
|
77
|
+
**Resolución:** Para nombres: verificar existencia antes de crear. Para versiones: hacer `reload` y reintentar el update.
|
|
78
|
+
|
|
79
|
+
### UnprocessableEntity (422)
|
|
80
|
+
|
|
81
|
+
**Causa:** Payload semánticamente inválido.
|
|
82
|
+
**Reproducción:** Crear servicio con imagen inexistente y restricciones de scheduling imposibles.
|
|
83
|
+
**Resolución:** Verificar que la imagen existe y que las constraints del servicio son alcanzables.
|
|
84
|
+
|
|
85
|
+
### TooManyRequests (429)
|
|
86
|
+
|
|
87
|
+
**Causa:** Rate limiting del Docker daemon o registry.
|
|
88
|
+
**Reproducción:** Burst de requests al API.
|
|
89
|
+
**Resolución:** Implementar backoff. Los retries de Excon cubren errores de socket/timeout pero no 429.
|
|
90
|
+
|
|
91
|
+
### InternalServerError (500)
|
|
92
|
+
|
|
93
|
+
**Causa:** Error interno del Docker daemon.
|
|
94
|
+
**Reproducción:** Bug en Docker, operación sobre estado inconsistente, o update sin `version` query param.
|
|
95
|
+
**Resolución:** Verificar logs del Docker daemon (`journalctl -u docker`). Si es por version faltante, usar el modelo (Updatable lo maneja).
|
|
96
|
+
|
|
97
|
+
### BadGateway (502)
|
|
98
|
+
|
|
99
|
+
**Causa:** Proxy o load balancer entre cliente y daemon devuelve error.
|
|
100
|
+
**Reproducción:** Docker daemon detrás de reverse proxy caído.
|
|
101
|
+
**Resolución:** Verificar infraestructura de red y proxy.
|
|
102
|
+
|
|
103
|
+
### ServiceUnavailable (503)
|
|
104
|
+
|
|
105
|
+
**Causa:** Docker daemon reiniciando o en mantenimiento.
|
|
106
|
+
**Reproducción:** Request durante restart del servicio Docker.
|
|
107
|
+
**Resolución:** Reintentar después de esperar. `max_retries` cubre errores de socket pero no 503.
|
|
108
|
+
|
|
109
|
+
### GatewayTimeout (504)
|
|
110
|
+
|
|
111
|
+
**Causa:** Proxy entre cliente y daemon timeout.
|
|
112
|
+
**Reproducción:** Operación larga detrás de proxy con timeout corto.
|
|
113
|
+
**Resolución:** Incrementar timeout del proxy. Verificar que `read_timeout` de la gema es menor que el del proxy.
|
|
114
|
+
|
|
115
|
+
### Communication
|
|
116
|
+
|
|
117
|
+
**Causa:** Error de socket o red — daemon caído, socket inexistente, permisos insuficientes.
|
|
118
|
+
**Reproducción:** `DockerSwarm::System.up` con daemon apagado.
|
|
119
|
+
**Resolución:** Verificar que Docker está corriendo (`systemctl status docker`), que el socket existe y que el usuario tiene permisos de lectura.
|
|
120
|
+
|
|
121
|
+
## Manejo recomendado
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
begin
|
|
125
|
+
DockerSwarm::Service.create(Name: "web", TaskTemplate: { ... })
|
|
126
|
+
rescue DockerSwarm::Conflict => e
|
|
127
|
+
# Nombre duplicado — buscar existente
|
|
128
|
+
existing = DockerSwarm::Service.all(name: ["web"]).first
|
|
129
|
+
rescue DockerSwarm::Communication => e
|
|
130
|
+
# Docker caído
|
|
131
|
+
logger.error("Docker unreachable: #{e.message}")
|
|
132
|
+
rescue DockerSwarm::Error => e
|
|
133
|
+
# Cualquier otro error de Docker
|
|
134
|
+
logger.error("Docker error: #{e.class} #{e.message}")
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Logging automático
|
|
139
|
+
|
|
140
|
+
El middleware ErrorHandler loguea automáticamente antes de raise:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
component=docker_swarm.middleware.error_handler event=business_error source=http status=409 message="name conflicts" method=post path=/services/create
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Formato KV con masking de datos sensibles via LogHelper.
|
|
147
|
+
|
|
148
|
+
## Causa original (Excon wrapping)
|
|
149
|
+
|
|
150
|
+
Excon puede envolver excepciones del middleware en `Excon::Error::Socket`. Connection detecta esto y re-raise la excepción original:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Connection#request internamente:
|
|
154
|
+
actual_error = e.cause&.class&.name&.include?("DockerSwarm::Error") ? e.cause : e
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
La excepción original mantiene la causa via `Exception#cause` de Ruby para trazabilidad completa.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docker-swarm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -117,6 +117,9 @@ files:
|
|
|
117
117
|
- lib/docker_swarm/models/task.rb
|
|
118
118
|
- lib/docker_swarm/models/volume.rb
|
|
119
119
|
- lib/docker_swarm/version.rb
|
|
120
|
+
- skill/SKILL.md
|
|
121
|
+
- skill/references/api-detallada.md
|
|
122
|
+
- skill/references/errores.md
|
|
120
123
|
homepage: https://github.com/wispro/docker-swarm
|
|
121
124
|
licenses:
|
|
122
125
|
- MIT
|