docker-swarm 0.6.0 → 0.7.1
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/CHANGELOG.md +126 -0
- data/LICENSE +21 -0
- data/README.md +31 -110
- data/docs/behavior/behavior.md +243 -0
- data/docs/config/config.md +140 -0
- data/docs/glossary/glossary.md +141 -0
- data/lib/docker_swarm/base.rb +11 -19
- data/lib/docker_swarm/concerns/updatable.rb +2 -1
- data/lib/docker_swarm/connection.rb +14 -7
- data/lib/docker_swarm/log_helper.rb +3 -1
- data/lib/docker_swarm/version.rb +1 -1
- data/skill/SKILL.md +130 -186
- metadata +16 -12
- data/skill/references/api-detallada.md +0 -257
- data/skill/references/errores.md +0 -157
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4fccd3ff16c561cf1f8563cf2dd518c69fd1a7fc46e9d74b7da804244dd1c4d
|
|
4
|
+
data.tar.gz: c71beba1337b83edde6eb18f424954e2563c35172e195615ed86b2b40153abe5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9165d28e2a0def50e674578600642dbaf3aca92b586bb5697590828a3081476139013922f45471dd84d45c376b997bd74bf78e3064d12ab14e3fd9bd046d2743
|
|
7
|
+
data.tar.gz: c2deb17e70f07a51b8e29ac69b9923706e8a57124fd3ddb4d36a30dded13de1fa4024d1a8c9dc1470cb984dd24da5a2a04cb7c6601a4022bcdecb84557516b79
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.7.1] — 2026-05-29
|
|
6
|
+
|
|
7
|
+
### Correcciones
|
|
8
|
+
- `Updatable#update`: captura `Version.Index` **antes** de `assign_attributes` para evitar que un payload con versión desactualizada pise el índice real del nodo y provoque `update out of sequence` en Docker — @Pablo
|
|
9
|
+
|
|
10
|
+
## [0.7.0] — 2026-05-21
|
|
11
|
+
|
|
12
|
+
### Breaking changes
|
|
13
|
+
- `Connection#request`: retries automáticos sólo en métodos seguros (GET/HEAD/PUT/DELETE/OPTIONS). POST/PATCH ya **no** reintentan para evitar recursos duplicados ante caída de socket — @Gabriel
|
|
14
|
+
- `Base#assign_attributes`: rama muerta que aceptaba no-Hash ahora levanta `ArgumentError` con mensaje claro — @Gabriel
|
|
15
|
+
- Ruby floor: `>= 3.2.0` (antes `>= 2.7.0`), alineado con `activesupport` 8.1 — @Gabriel
|
|
16
|
+
|
|
17
|
+
### Correcciones
|
|
18
|
+
- `Connection#request`: clasificación de errores por `is_a?(DockerSwarm::Error)` en vez de `class.name.include?`, evita falsos positivos con clases externas — @Gabriel
|
|
19
|
+
- `LogHelper::SENSITIVE_KEYS`: `data` ahora se matchea con `\b` para no filtrar `metadata`, `database` u otras claves legítimas — @Gabriel
|
|
20
|
+
|
|
21
|
+
### Documentación
|
|
22
|
+
- Re-estructura completa bajo skills `dev-*` (RFC-001/007/008/009/010): nuevos `docs/glossary/glossary.md` (20 términos) y `docs/behavior/behavior.md` (8 flujos load-bearing con Mermaid). `README.md` minimal-control y `skill/SKILL.md` con frontmatter, contrato resumido, version-lock — @Gabriel
|
|
23
|
+
- Eliminados docs ad-hoc pre-estándar (`docs/{api,configuration,errors,models,testing}.md` y `skill/references/`) — @Gabriel
|
|
24
|
+
|
|
25
|
+
### Mejoras internas
|
|
26
|
+
- CI: tests unitarios en `bundle exec rspec --tag ~type:integration` para que el build verde sea significativo sin Docker disponible — @Gabriel
|
|
27
|
+
- Gemspec: `metadata` con `changelog_uri`/`bug_tracker_uri`/`documentation_uri`/`rubygems_mfa_required` version-locked; `LICENSE`, `CHANGELOG.md` y `docs/**/*` incluidos en `spec.files` para que viajen con el release — @Gabriel
|
|
28
|
+
- `LICENSE` (MIT) agregado al repo — @Gabriel
|
|
29
|
+
- Removido `debug_docker.rb` del repo (artefacto de desarrollo) — @Gabriel
|
|
30
|
+
|
|
31
|
+
## [0.6.0] - 2026-04-08
|
|
32
|
+
|
|
33
|
+
### Nuevas funcionalidades
|
|
34
|
+
- `Service#restart`: reinicia un servicio incrementando `ForceUpdate` en el `TaskTemplate`, equivalente a `docker service update --force` — @Gabriel
|
|
35
|
+
|
|
36
|
+
## [0.5.4] - 2026-04-07
|
|
37
|
+
|
|
38
|
+
### Correcciones
|
|
39
|
+
- `Inspectable`: todos los atributos deben usar `send(:attr)` para invocar métodos dinámicos — @Gabriel
|
|
40
|
+
|
|
41
|
+
## [0.5.3] - 2026-04-07
|
|
42
|
+
|
|
43
|
+
### Mejoras internas
|
|
44
|
+
- `Inspectable` ahora muestra todos los atributos (ID, Name, CreatedAt, UpdatedAt, Version, Spec) — @Gabriel
|
|
45
|
+
|
|
46
|
+
## [0.5.2] - 2026-04-07
|
|
47
|
+
|
|
48
|
+
### Correcciones
|
|
49
|
+
- Fix en `Inspectable`: usar `send(:ID)` en vez de `ID` directamente para evitar `NameError` por interpretación como constante — @Gabriel
|
|
50
|
+
|
|
51
|
+
## [0.5.1] - 2026-04-06
|
|
52
|
+
|
|
53
|
+
### Mejoras internas
|
|
54
|
+
- Refactor de logs en `Connection`: cambio de `duration_ms` a `duration_s` y de `status` a `http_status` para alinearse con estándares de observabilidad — @Gabriel
|
|
55
|
+
- Precisión de duración mejorada usando segundos con 4 decimales — @Gabriel
|
|
56
|
+
- Reestructuración de `skills.yml` para soportar variables de entorno en agentes y nuevas skills — @Gabriel
|
|
57
|
+
|
|
58
|
+
## [0.5.0] - 2026-04-04
|
|
59
|
+
|
|
60
|
+
### Nuevas funcionalidades
|
|
61
|
+
- Skill de conocimiento empaquetada (`skill/`) para consumo via `skill-manager sync` — @Gabriel
|
|
62
|
+
- Helper de testing documentado (`DockerSwarmHelpers`) con `stub_docker_find` y `stub_docker_list` — @Claude
|
|
63
|
+
|
|
64
|
+
### Mejoras internas
|
|
65
|
+
- README reescrito con instalación, más ejemplos de uso, y sección Documentación linkeando a `docs/` — @Claude
|
|
66
|
+
- `docs/testing.md` reescrito: mockeo de CRUD, errores, helper reutilizable — @Claude
|
|
67
|
+
- `docs/errors.md` completado con errores faltantes (NotAcceptable 406, RequestTimeout 408, BadGateway 502) — @Claude
|
|
68
|
+
- Cross-references bidireccionales entre todos los docs — @Claude
|
|
69
|
+
- Centralización de logging en `LogHelper` con masking de campo `Data` para Secret/Config — @Gabriel
|
|
70
|
+
- Gemspec actualizado para empaquetar `skill/**/*` en el `.gem` — @Claude
|
|
71
|
+
|
|
72
|
+
## [0.4.0] - 2026-03-31
|
|
73
|
+
|
|
74
|
+
### Nuevas funcionalidades
|
|
75
|
+
- Dependabot configurado para actualizaciones semanales de gemas y mensuales de GitHub Actions — @Gabriel
|
|
76
|
+
- Integración de `rubocop-rails-omakase` y verificación RuboCop en CI — @Gabriel
|
|
77
|
+
- Tests de infraestructura robustos para `Connection`, `Configuration` y Middlewares — @Gabriel
|
|
78
|
+
- Documentación YARD en todos los modelos y componentes core — @Gabriel
|
|
79
|
+
- Concern `Inspectable` para inspección legible en consola (ID, Name, Image) — @Gabriel
|
|
80
|
+
|
|
81
|
+
### Mejoras internas
|
|
82
|
+
- Memoización de accessors en `Base` para optimizar procesamiento de recursos — @Gabriel
|
|
83
|
+
- Refactor de logs a `Concerns::Loggable` para DRY en Service, Task y Container — @Gabriel
|
|
84
|
+
- Branch default renombrado de `master` a `main` — @Gabriel
|
|
85
|
+
|
|
86
|
+
### Correcciones
|
|
87
|
+
- Casting automático de timeouts y retries para prevenir `TypeError` con ENV variables — @Gabriel
|
|
88
|
+
- Orden de carga interno reorganizado para resolver issues de inicialización — @Gabriel
|
|
89
|
+
|
|
90
|
+
## [0.3.0] - 2026-03-31
|
|
91
|
+
|
|
92
|
+
### Nuevas funcionalidades
|
|
93
|
+
- Timeouts y retries configurables: `read_timeout`, `write_timeout`, `connect_timeout`, `max_retries` — @Gabriel
|
|
94
|
+
- `RequestEncoder` soporta `application/x-www-form-urlencoded` y `multipart/form-data` — @Gabriel
|
|
95
|
+
- `ResponseJSONParser` aplica `with_indifferent_access` recursivo en Arrays — @Gabriel
|
|
96
|
+
|
|
97
|
+
## [0.2.0] - 2026-03-31
|
|
98
|
+
|
|
99
|
+
### Nuevas funcionalidades
|
|
100
|
+
- Logging estructurado KV (`component`, `event`, `source`, `duration_ms`) estándar Wispro — @Gabriel
|
|
101
|
+
- Reloj monotónico para medición precisa de duración de requests — @Gabriel
|
|
102
|
+
- Masking automático de claves sensibles en logs — @Gabriel
|
|
103
|
+
- Soporte para Unix sockets y HTTP/TCP via `socket_path` — @Gabriel
|
|
104
|
+
- `Network.update` implementado — @Gabriel
|
|
105
|
+
- `log_level` configurable en runtime — @Gabriel
|
|
106
|
+
- Error `TooManyRequests` (429) — @Gabriel
|
|
107
|
+
- Tests de integración para Service, Node, Task, Swarm, System, Image y Container — @Gabriel
|
|
108
|
+
|
|
109
|
+
### Mejoras internas
|
|
110
|
+
- Modelos movidos a `lib/docker_swarm/models/` — @Gabriel
|
|
111
|
+
- `Base.all` normalizado para respuestas envueltas (`root_key`) — @Gabriel
|
|
112
|
+
- Método `logs` estandarizado a nivel de instancia — @Gabriel
|
|
113
|
+
- `Swarm` y `System` heredan de `Base` con atributos dinámicos — @Gabriel
|
|
114
|
+
- Jerarquía de errores reestructurada bajo `DockerSwarm::Error` con aliases — @Gabriel
|
|
115
|
+
|
|
116
|
+
### Correcciones
|
|
117
|
+
- Excon ya no envuelve excepciones de negocio en `Excon::Error::Socket` — @Gabriel
|
|
118
|
+
- `Volume.all` corregido para la respuesta envuelta de Docker — @Gabriel
|
|
119
|
+
- `Gateway_Timeout` renombrado a `GatewayTimeout` — @Gabriel
|
|
120
|
+
- Entry point `lib/docker-swarm.rb` agregado para Bundler/Rails — @Gabriel
|
|
121
|
+
- Validaciones de `Service` relajadas para flexibilidad en tests — @Gabriel
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## [0.1.0] - Early 2026
|
|
126
|
+
- Release inicial con ORM básico para Services, Networks y Volumes — @Gabriel
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gabriel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,145 +1,66 @@
|
|
|
1
|
-
#
|
|
1
|
+
# docker-swarm
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/docker-swarm)
|
|
4
|
-
[](
|
|
4
|
+
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
## Propósito
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
ORM ligero y cliente API para Docker Swarm en Ruby. Expone las primitivas del Docker Engine (Service, Node, Task, Container, Network, Volume, Config, Secret, Swarm, System, Image) como modelos `ActiveModel` con CRUD, validaciones y logs estructurados KV. Excon directo sobre el socket Unix (default) o TCP.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Setup
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
13
|
+
# Gemfile
|
|
13
14
|
gem 'docker-swarm', '~> 0.6'
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
Y ejecuta:
|
|
17
|
-
|
|
18
17
|
```bash
|
|
19
18
|
bundle install
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Quick start:
|
|
23
22
|
|
|
24
23
|
```ruby
|
|
25
24
|
require 'docker_swarm'
|
|
26
25
|
|
|
27
|
-
# Configurar (opcional, usa defaults razonables)
|
|
28
26
|
DockerSwarm.configure do |config|
|
|
29
|
-
config.socket_path = "unix:///var/run/docker.sock"
|
|
27
|
+
config.socket_path = "unix:///var/run/docker.sock" # default
|
|
30
28
|
config.log_level = Logger::INFO
|
|
31
|
-
config.max_retries = 3
|
|
32
29
|
end
|
|
33
30
|
|
|
34
|
-
#
|
|
35
|
-
DockerSwarm::
|
|
36
|
-
|
|
37
|
-
# Listar servicios
|
|
38
|
-
services = DockerSwarm::Service.all
|
|
39
|
-
services.each { |s| puts "#{s.ID}: #{s.Spec[:Name]}" }
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Uso
|
|
43
|
-
|
|
44
|
-
### Crear un servicio
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
service = DockerSwarm::Service.create(
|
|
48
|
-
Name: "my-webapp",
|
|
49
|
-
TaskTemplate: {
|
|
50
|
-
ContainerSpec: { Image: "nginx:latest" }
|
|
51
|
-
}
|
|
52
|
-
)
|
|
53
|
-
puts service.ID
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Actualizar (maneja Version.Index automaticamente)
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
service = DockerSwarm::Service.find("svc-id")
|
|
60
|
-
service.update(Mode: { Replicated: { Replicas: 3 } })
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Eliminar
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
service.destroy # graceful: retorna nil si ya no existe
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Reiniciar
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
service.restart # equivalente a docker service update --force
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Logs
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
puts service.logs(stdout: 1, stderr: 1)
|
|
31
|
+
DockerSwarm::System.up # => "OK"
|
|
32
|
+
DockerSwarm::Service.all # => [#<Service ...>, ...]
|
|
33
|
+
DockerSwarm::Service.find("svc-id") # => nil si no existe
|
|
79
34
|
```
|
|
80
35
|
|
|
81
|
-
|
|
36
|
+
Para el contrato completo de la gema (símbolos públicos, gotchas, integración) ver [`skill/SKILL.md`](skill/SKILL.md).
|
|
82
37
|
|
|
83
|
-
|
|
84
|
-
DockerSwarm::Service.all(label: ["env=production"])
|
|
85
|
-
DockerSwarm::Node.all(role: ["manager"])
|
|
86
|
-
DockerSwarm::Container.all(status: ["running"])
|
|
87
|
-
```
|
|
38
|
+
## Índice de artefactos
|
|
88
39
|
|
|
89
|
-
|
|
40
|
+
Documentación normada (RFC-001) por capa:
|
|
90
41
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
42
|
+
| Capa | Artefacto | Estado |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Datos | — | `n/a` (gema sin DB) |
|
|
45
|
+
| Glosario | [`docs/glossary/glossary.md`](docs/glossary/glossary.md) | F1 completo |
|
|
46
|
+
| Comportamiento | [`docs/behavior/behavior.md`](docs/behavior/behavior.md) | F1 backfill on-demand (8 flujos) |
|
|
47
|
+
| API (operaciones) | — | F2 pendiente; contrato resumido inline en `skill/SKILL.md` |
|
|
48
|
+
| Interfaz | — | F2 pendiente; contrato resumido inline en `skill/SKILL.md` |
|
|
49
|
+
| Topología | — | F2 pendiente |
|
|
50
|
+
| Eventos | — | `n/a` (la gema no emite eventos) |
|
|
97
51
|
|
|
98
|
-
|
|
52
|
+
`n/a` = no aplica al tipo de repo. F2 pendiente = capa declarada en la RFC pero todavía no implementada en `dev-structure`; el contenido relevante vive transitoriamente en `skill/SKILL.md` (RFC-008 §2 coexistencia transitoria).
|
|
99
53
|
|
|
100
|
-
|
|
101
|
-
begin
|
|
102
|
-
DockerSwarm::Service.create(Name: "web", TaskTemplate: { ... })
|
|
103
|
-
rescue DockerSwarm::Conflict => e
|
|
104
|
-
puts "Nombre duplicado"
|
|
105
|
-
rescue DockerSwarm::Communication => e
|
|
106
|
-
puts "Docker inalcanzable: #{e.message}"
|
|
107
|
-
end
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Configuracion
|
|
111
|
-
|
|
112
|
-
```ruby
|
|
113
|
-
DockerSwarm.configure do |config|
|
|
114
|
-
config.socket_path = "unix:///var/run/docker.sock" # o http://host:port
|
|
115
|
-
config.logger = Logger.new($stdout)
|
|
116
|
-
config.log_level = Logger::INFO
|
|
117
|
-
config.read_timeout = 60 # segundos
|
|
118
|
-
config.write_timeout = 60
|
|
119
|
-
config.connect_timeout = 10
|
|
120
|
-
config.max_retries = 3
|
|
121
|
-
end
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Ver [Guia de Configuracion](docs/configuration.md) para opciones avanzadas, Rails integration y observabilidad.
|
|
125
|
-
|
|
126
|
-
## Documentacion
|
|
127
|
-
|
|
128
|
-
- [Modelos (ORM)](docs/models.md) — Todos los modelos, concerns y ciclo de vida
|
|
129
|
-
- [Configuracion](docs/configuration.md) — Opciones, observabilidad y seguridad
|
|
130
|
-
- [Manejo de Errores](docs/errors.md) — Jerarquia de excepciones y uso
|
|
131
|
-
- [Cliente API](docs/api.md) — Acceso de bajo nivel y middlewares
|
|
132
|
-
- [Testing](docs/testing.md) — Estrategias de mocking para tus tests
|
|
133
|
-
|
|
134
|
-
## Contribuir
|
|
135
|
-
|
|
136
|
-
Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las guias de desarrollo.
|
|
54
|
+
## Desarrollo
|
|
137
55
|
|
|
138
56
|
```bash
|
|
139
|
-
bundle exec rspec
|
|
140
|
-
bundle exec
|
|
57
|
+
bundle exec rspec --tag ~type:integration # unit suite
|
|
58
|
+
bundle exec rspec # incluye integration (requiere Docker socket)
|
|
59
|
+
bundle exec rubocop -a # lint
|
|
141
60
|
```
|
|
142
61
|
|
|
62
|
+
Release: `/gem-release` (publica a RubyGems via GitHub Action al tag `v*`).
|
|
63
|
+
|
|
143
64
|
## Licencia
|
|
144
65
|
|
|
145
|
-
|
|
66
|
+
MIT — ver [LICENSE](LICENSE).
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Comportamiento — docker-swarm
|
|
2
|
+
|
|
3
|
+
> meta: artefacto · RFC-007 · generado dev-enrich · anclado a `a4e3129` · cobertura: backfill on-demand inicial (8 flujos load-bearing)
|
|
4
|
+
|
|
5
|
+
## 1. Resumen
|
|
6
|
+
|
|
7
|
+
Flujos de ejecución load-bearing de `docker-swarm`: cómo se materializan en runtime las operaciones CRUD, el control de concurrencia optimista de Docker, la política de retries post-fix correctness, y el mapeo de errores. Render Mermaid sequenceDiagram/flowchart por flujo.
|
|
8
|
+
|
|
9
|
+
## 2. Cobertura declarada
|
|
10
|
+
|
|
11
|
+
### Documentados (8)
|
|
12
|
+
|
|
13
|
+
1. `Service.create` + reload
|
|
14
|
+
2. `Service.update` con `Version.Index`
|
|
15
|
+
3. `Service.restart` (force update)
|
|
16
|
+
4. `Model.destroy` (graceful 404)
|
|
17
|
+
5. Política de retries por método HTTP
|
|
18
|
+
6. Error mapping (HTTP status → excepción tipada)
|
|
19
|
+
7. `Container.start` / `Container.stop`
|
|
20
|
+
8. `Loggable#logs` streaming
|
|
21
|
+
|
|
22
|
+
### No documentados (ausencia ≠ inexistencia, RFC-007)
|
|
23
|
+
|
|
24
|
+
- Reconexión / reapertura de socket Unix (Excon nativo, fuera de nuestra superficie).
|
|
25
|
+
- Flujo de configuración / boot (`DockerSwarm.configure`) — trivial, sin secuencia de interés.
|
|
26
|
+
- Pull de imágenes con autenticación de registry (`X-Registry-Auth`) — **no implementado** en la gema (gap conocido).
|
|
27
|
+
- `Container.create` — **no implementado** en F1 (intencional, ver glossary).
|
|
28
|
+
|
|
29
|
+
## 3. Flujos
|
|
30
|
+
|
|
31
|
+
### 3.1 `Service.create` + reload
|
|
32
|
+
|
|
33
|
+
Crear un Service implica un POST seguido de un reload automático para hidratar la instancia con la respuesta completa de Docker (el endpoint `create` sólo devuelve `ID`).
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
sequenceDiagram
|
|
37
|
+
actor Caller
|
|
38
|
+
participant Service as DockerSwarm::Service
|
|
39
|
+
participant Api as DockerSwarm::Api
|
|
40
|
+
participant Conn as Connection
|
|
41
|
+
participant Docker as Docker Engine
|
|
42
|
+
|
|
43
|
+
Caller->>Service: Service.create(Name:, TaskTemplate:)
|
|
44
|
+
Service->>Service: new(attrs) → valid?
|
|
45
|
+
Service->>Api: request(:create, payload: payload_for_docker)
|
|
46
|
+
Api->>Conn: request(method: :post, path: services/create, body:)
|
|
47
|
+
Conn->>Docker: POST /services/create
|
|
48
|
+
Docker-->>Conn: 201 { ID: "abc" }
|
|
49
|
+
Conn-->>Service: { ID: "abc" }
|
|
50
|
+
Service->>Service: self.ID = "abc"
|
|
51
|
+
Service->>Api: request(:show, arguments: { id: "abc" })
|
|
52
|
+
Api->>Conn: request(method: :get, path: services/abc)
|
|
53
|
+
Conn->>Docker: GET /services/abc
|
|
54
|
+
Docker-->>Conn: 200 { ID, Spec, Version, ... }
|
|
55
|
+
Conn-->>Service: full payload
|
|
56
|
+
Service-->>Caller: hydrated Service instance
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Notas load-bearing:**
|
|
60
|
+
- `valid?` corre antes del POST. Si falla, `save` retorna `false` sin tocar el API.
|
|
61
|
+
- El POST no se reintenta automáticamente: ver §3.5 (retry policy).
|
|
62
|
+
- El reload usa `find` interno; si Docker devuelve 404 entre create+show (caso extremo), el reload no se realiza y la instancia queda sólo con `ID`.
|
|
63
|
+
|
|
64
|
+
### 3.2 `Service.update` con `Version.Index`
|
|
65
|
+
|
|
66
|
+
Updates atómicos sobre Service/Node/Network requieren enviar el `Version.Index` actual como query param. Sin él, Docker rechaza con 500. La gema lo extrae automáticamente de `self.Version["Index"]`.
|
|
67
|
+
|
|
68
|
+
```mermaid
|
|
69
|
+
sequenceDiagram
|
|
70
|
+
actor Caller
|
|
71
|
+
participant Model as Service/Node/Network
|
|
72
|
+
participant Api
|
|
73
|
+
participant Docker as Docker Engine
|
|
74
|
+
|
|
75
|
+
Caller->>Model: model.update(Mode: { Replicated: { Replicas: 3 } })
|
|
76
|
+
Model->>Model: assign_attributes(new_attrs) -- Spec deep_merge si aplica
|
|
77
|
+
Model->>Model: valid?
|
|
78
|
+
Model->>Api: request(:update, id:, query: { version: Version.Index }, payload:)
|
|
79
|
+
Api->>Docker: POST /services/abc/update?version=42
|
|
80
|
+
alt version coincide
|
|
81
|
+
Docker-->>Api: 200 OK
|
|
82
|
+
Api-->>Model: success
|
|
83
|
+
Model-->>Caller: true
|
|
84
|
+
else version stale
|
|
85
|
+
Docker-->>Api: 500 update out of sequence
|
|
86
|
+
Api->>Api: ErrorHandler → raise InternalServerError
|
|
87
|
+
Api-->>Caller: DockerSwarm::Error::InternalServerError
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Notas load-bearing:**
|
|
92
|
+
- `assign_attributes` muta el estado local **antes** de validar/enviar. Si el update falla, la instancia queda mutada (gotcha conocido, ver SKILL.md).
|
|
93
|
+
- `Spec` se mergea con `deep_merge`; otros atributos top-level se asignan directo.
|
|
94
|
+
- El update **no** hace reload automático (a diferencia de `create`). El caller llama `reload` si necesita el `Version.Index` nuevo.
|
|
95
|
+
|
|
96
|
+
### 3.3 `Service.restart` (force update)
|
|
97
|
+
|
|
98
|
+
Reinicio sin downtime equivalente a `docker service update --force`: incrementa `ForceUpdate` del `TaskTemplate` para que Docker recree todas las tasks.
|
|
99
|
+
|
|
100
|
+
```mermaid
|
|
101
|
+
flowchart LR
|
|
102
|
+
A["service.restart"] --> B["lee Spec.TaskTemplate.ForceUpdate"]
|
|
103
|
+
B --> C["current = ForceUpdate o 0 si nil"]
|
|
104
|
+
C --> D["update Spec: TaskTemplate: ForceUpdate: current+1"]
|
|
105
|
+
D --> E["POST /services/id/update?version=X"]
|
|
106
|
+
E --> F["Docker recrea tasks"]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Notas load-bearing:**
|
|
110
|
+
- Usa el flujo §3.2 internamente — hereda Version.Index, retry-policy y error-mapping.
|
|
111
|
+
- Si `Spec` o `TaskTemplate` no existen aún (instancia nueva sin reload), `current = 0`.
|
|
112
|
+
|
|
113
|
+
### 3.4 `Model.destroy` (graceful 404)
|
|
114
|
+
|
|
115
|
+
DELETE con tolerancia a 404: si el recurso ya fue eliminado, `destroy` retorna `nil` en vez de raise.
|
|
116
|
+
|
|
117
|
+
```mermaid
|
|
118
|
+
sequenceDiagram
|
|
119
|
+
actor Caller
|
|
120
|
+
participant Model
|
|
121
|
+
participant Api
|
|
122
|
+
participant Docker
|
|
123
|
+
|
|
124
|
+
Caller->>Model: model.destroy
|
|
125
|
+
Model->>Api: request(:destroy, id:)
|
|
126
|
+
Api->>Docker: DELETE /services/abc
|
|
127
|
+
alt recurso existe
|
|
128
|
+
Docker-->>Api: 200 OK
|
|
129
|
+
Api-->>Model: true
|
|
130
|
+
Model-->>Caller: true
|
|
131
|
+
else 404 (ya eliminado)
|
|
132
|
+
Docker-->>Api: 404 Not Found
|
|
133
|
+
Api->>Api: ErrorHandler → raise NotFound
|
|
134
|
+
Model->>Model: rescue Errors::NotFound
|
|
135
|
+
Model-->>Caller: nil
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Notas load-bearing:**
|
|
140
|
+
- Tanto `Model.destroy(id)` (class method) como `model.destroy` (instance) capturan `NotFound`.
|
|
141
|
+
- 409 (`Conflict` — recurso en uso) **no** se captura; el caller debe manejarlo si aplica.
|
|
142
|
+
|
|
143
|
+
### 3.5 Política de retries por método HTTP
|
|
144
|
+
|
|
145
|
+
Post-fix correctness (PR #3): los retries automáticos sólo aplican a métodos seguros. POST/PATCH no reintentan para evitar duplicados.
|
|
146
|
+
|
|
147
|
+
```mermaid
|
|
148
|
+
flowchart TD
|
|
149
|
+
Req[Connection#request<br/>method, path, body] --> Class{IDEMPOTENT_METHODS<br/>incluye method?}
|
|
150
|
+
Class -- get/head/put/delete/options --> Safe[idempotent: true<br/>retries: max_retries]
|
|
151
|
+
Class -- post/patch/otro --> Write[idempotent: false<br/>retries: 0]
|
|
152
|
+
Safe --> Excon[Excon.request<br/>retry on Socket/Timeout]
|
|
153
|
+
Write --> ExconNoRetry[Excon.request<br/>sin retry]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Notas load-bearing:**
|
|
157
|
+
- Si el socket se cierra durante la lectura de la respuesta de un `services/create`, **no** se replica — la instancia queda en error y el caller decide.
|
|
158
|
+
- `max_retries` (default 3) sólo afecta lecturas/idempotentes.
|
|
159
|
+
|
|
160
|
+
### 3.6 Error mapping (status → excepción)
|
|
161
|
+
|
|
162
|
+
`ErrorHandler` interpreta status HTTP 4xx/5xx y emite excepción tipada de la jerarquía `DockerSwarm::Error`.
|
|
163
|
+
|
|
164
|
+
```mermaid
|
|
165
|
+
flowchart TD
|
|
166
|
+
Resp[Response status, body] --> Range{status range}
|
|
167
|
+
Range -- 200-299 --> Pass[pass to next middleware]
|
|
168
|
+
Range -- 4xx/5xx --> Log[log business_error<br/>KV format]
|
|
169
|
+
Log --> Map{status}
|
|
170
|
+
Map -- 400 --> BadRequest
|
|
171
|
+
Map -- 401 --> Unauthorized
|
|
172
|
+
Map -- 403 --> Forbidden
|
|
173
|
+
Map -- 404 --> NotFound
|
|
174
|
+
Map -- 406 --> NotAcceptable
|
|
175
|
+
Map -- 408 --> RequestTimeout
|
|
176
|
+
Map -- 409 --> Conflict
|
|
177
|
+
Map -- 422 --> UnprocessableEntity
|
|
178
|
+
Map -- 429 --> TooManyRequests
|
|
179
|
+
Map -- 500 --> InternalServerError
|
|
180
|
+
Map -- 502 --> BadGateway
|
|
181
|
+
Map -- 503 --> ServiceUnavailable
|
|
182
|
+
Map -- 504 --> GatewayTimeout
|
|
183
|
+
Map -- otro --> GenericError[Error HTTP N]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Notas load-bearing:**
|
|
187
|
+
- Antes de raise, `ErrorHandler` emite un evento `business_error` en formato KV con `status`, `message`, `method`, `path`. Permite diagnosticar sin abrir backtrace.
|
|
188
|
+
- Errores de red (no HTTP) se mapean a `DockerSwarm::Error::Communication` en `Connection#request`.
|
|
189
|
+
- El mensaje de la excepción viene del body parseado: prefiere `body["message"]`, luego `body["error"]`, luego el body completo.
|
|
190
|
+
|
|
191
|
+
### 3.7 `Container.start` / `Container.stop`
|
|
192
|
+
|
|
193
|
+
Operaciones específicas sin payload (POST a endpoint dedicado).
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
sequenceDiagram
|
|
197
|
+
actor Caller
|
|
198
|
+
participant Container as DockerSwarm::Container
|
|
199
|
+
participant Api
|
|
200
|
+
participant Docker
|
|
201
|
+
|
|
202
|
+
Caller->>Container: container.start
|
|
203
|
+
Container->>Api: request(:start, id:)
|
|
204
|
+
Api->>Docker: POST /containers/abc/start
|
|
205
|
+
Docker-->>Api: 204 No Content
|
|
206
|
+
Api-->>Container: ""
|
|
207
|
+
Container-->>Caller: true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Mismo patrón para `stop`. POST sin body → no se reintenta automáticamente (§3.5).
|
|
211
|
+
|
|
212
|
+
### 3.8 `Loggable#logs` streaming
|
|
213
|
+
|
|
214
|
+
Obtención de logs raw para Service/Task/Container.
|
|
215
|
+
|
|
216
|
+
```mermaid
|
|
217
|
+
sequenceDiagram
|
|
218
|
+
actor Caller
|
|
219
|
+
participant Model as Service/Task/Container
|
|
220
|
+
participant Api
|
|
221
|
+
participant Docker
|
|
222
|
+
|
|
223
|
+
Caller->>Model: model.logs(stdout: 1, stderr: 1, follow: 0)
|
|
224
|
+
Model->>Api: request(:logs, id:, query: { stdout:, stderr:, follow: })
|
|
225
|
+
Api->>Docker: GET /services/abc/logs?stdout=1&stderr=1
|
|
226
|
+
Docker-->>Api: 200 raw stream (text/plain)
|
|
227
|
+
Api-->>Model: raw body
|
|
228
|
+
Model-->>Caller: String
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Notas load-bearing:**
|
|
232
|
+
- El body se devuelve sin parseo (no es JSON; `ResponseJSONParser` lo respeta porque Content-Type no es `application/json`).
|
|
233
|
+
- `follow: 1` mantiene la conexión abierta — el caller debe manejar el stream/timeout.
|
|
234
|
+
|
|
235
|
+
## 4. Cobertura y fronteras
|
|
236
|
+
|
|
237
|
+
- **Cobertura inicial (RFC-007 backfill on-demand):** 8 flujos load-bearing documentados. Esta gema es chica; el backfill completo es factible y se hace ahora.
|
|
238
|
+
- **Frontera con glosario:** términos (Service, Spec, Version.Index, etc.) viven en [`docs/glossary/glossary.md`](../glossary/glossary.md). Esta capa documenta secuencias, no significado.
|
|
239
|
+
- **Frontera con configuración:** `DockerSwarm.configure` es boot, no flujo de negocio. No se diagrama.
|
|
240
|
+
- **No localizable / fuera de alcance:**
|
|
241
|
+
- Lógica interna de Excon (retry timing, socket pool) — vive en Excon, no se inventa diagrama.
|
|
242
|
+
- Flujo de auth registry para `Image.create` — la gema **no implementa** `X-Registry-Auth` (gap, no flujo a documentar).
|
|
243
|
+
- **Cadencia incremental a partir de acá:** sólo se diagrama un flujo cuando un PR lo toca o agrega. No barrido retroactivo de legacy.
|