docker-swarm 0.1.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 +7 -0
- data/README.md +63 -0
- data/lib/docker_swarm/api.rb +84 -0
- data/lib/docker_swarm/base.rb +156 -0
- data/lib/docker_swarm/concerns/creatable.rb +31 -0
- data/lib/docker_swarm/concerns/deletable.rb +22 -0
- data/lib/docker_swarm/concerns/updatable.rb +23 -0
- data/lib/docker_swarm/connection.rb +38 -0
- data/lib/docker_swarm/errors.rb +22 -0
- data/lib/docker_swarm/middleware/error_handler.rb +46 -0
- data/lib/docker_swarm/middleware/request_encoder.rb +15 -0
- data/lib/docker_swarm/middleware/response_json_parser.rb +25 -0
- data/lib/docker_swarm/models/config.rb +8 -0
- data/lib/docker_swarm/models/container.rb +19 -0
- data/lib/docker_swarm/models/image.rb +8 -0
- data/lib/docker_swarm/models/network.rb +18 -0
- data/lib/docker_swarm/models/node.rb +8 -0
- data/lib/docker_swarm/models/secret.rb +8 -0
- data/lib/docker_swarm/models/service.rb +27 -0
- data/lib/docker_swarm/models/swarm.rb +13 -0
- data/lib/docker_swarm/models/system.rb +25 -0
- data/lib/docker_swarm/models/task.rb +9 -0
- data/lib/docker_swarm/models/volume.rb +16 -0
- data/lib/docker_swarm/version.rb +5 -0
- data/lib/docker_swarm.rb +62 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 97102c26aff73c5426373e1cfbfb542fd2cf4e82404a3b1de4833b8e82d5d66a
|
|
4
|
+
data.tar.gz: 3a9288b1dc9eae6d4849503de616561c94af264ec467e9450c2a4e2863e4706f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fb9eea1d60221c643681e50ea18a4a9830967e46ef226543c62df85c26102c9fd60b26a6f77f35f12c6e5ff86ca15ec280175ca656c266587e7737414837f91c
|
|
7
|
+
data.tar.gz: 0b223dd63b9c69a14a24c07b3be527876d9535c1114f53a507152fd2d28cbf126a387b0b72ddbba18290aa621c49369adaf83f2e80ca68f3d2dad8def9c749bf
|
data/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Docker Swarm Gem
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/docker-swarm)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
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.
|
|
7
|
+
|
|
8
|
+
## 馃殌 Inicio R谩pido
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
require 'docker_swarm'
|
|
12
|
+
|
|
13
|
+
# Configurar (opcional, usa defaults)
|
|
14
|
+
DockerSwarm.configure do |config|
|
|
15
|
+
config.socket_path = "unix:///var/run/docker.sock"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Listar servicios
|
|
19
|
+
services = DockerSwarm::Service.all
|
|
20
|
+
services.each { |s| puts "#{s.ID}: #{s.Spec['Name']}" }
|
|
21
|
+
|
|
22
|
+
# Crear un nuevo servicio
|
|
23
|
+
service = DockerSwarm::Service.create(
|
|
24
|
+
Spec: {
|
|
25
|
+
Name: "my-webapp",
|
|
26
|
+
TaskTemplate: {
|
|
27
|
+
ContainerSpec: { Image: "nginx:latest" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Obtener logs
|
|
33
|
+
puts service.logs
|
|
34
|
+
```
|
|
35
|
+
|
|
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
|
+
## 馃洜 Caracter铆sticas Clave
|
|
47
|
+
|
|
48
|
+
- **Mapeo PascalCase**: Mantiene la fidelidad con los atributos de Docker (e.g., `s.ID`, `s.Spec`) evitando transformaciones costosas.
|
|
49
|
+
- **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
|
+
|
|
53
|
+
## 馃 Contribuir
|
|
54
|
+
|
|
55
|
+
Las contribuciones son bienvenidas. Por favor, lee `CLAUDE.md` para las gu铆as de desarrollo y aseg煤rate de que todos los tests pasen antes de enviar un PR.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bundle exec rspec
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 馃搫 Licencia
|
|
62
|
+
|
|
63
|
+
Este proyecto est谩 bajo la licencia MIT.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Api
|
|
5
|
+
ENDPOINTS = {
|
|
6
|
+
swarm: { show: { method: :get, path: "swarm" } },
|
|
7
|
+
system: {
|
|
8
|
+
info: { method: :get, path: "info" },
|
|
9
|
+
version: { method: :get, path: "version" },
|
|
10
|
+
up: { method: :get, path: "_ping" },
|
|
11
|
+
df: { method: :get, path: "system/df" }
|
|
12
|
+
},
|
|
13
|
+
nodes: {
|
|
14
|
+
index: { method: :get, path: "nodes" },
|
|
15
|
+
show: { method: :get, path: "nodes/%<id>s" },
|
|
16
|
+
update: { method: :post, path: "nodes/%<id>s/update" },
|
|
17
|
+
destroy: { method: :delete, path: "nodes/%<id>s" }
|
|
18
|
+
},
|
|
19
|
+
tasks: {
|
|
20
|
+
index: { method: :get, path: "tasks" },
|
|
21
|
+
show: { method: :get, path: "tasks/%<id>s" },
|
|
22
|
+
logs: { method: :get, path: "tasks/%<id>s/logs" }
|
|
23
|
+
},
|
|
24
|
+
services: {
|
|
25
|
+
index: { method: :get, path: "services" },
|
|
26
|
+
show: { method: :get, path: "services/%<id>s" },
|
|
27
|
+
create: { method: :post, path: "services/create" },
|
|
28
|
+
update: { method: :post, path: "services/%<id>s/update" },
|
|
29
|
+
destroy: { method: :delete, path: "services/%<id>s" },
|
|
30
|
+
logs: { method: :get, path: "services/%<id>s/logs" }
|
|
31
|
+
},
|
|
32
|
+
configs: {
|
|
33
|
+
index: { method: :get, path: "configs" },
|
|
34
|
+
show: { method: :get, path: "configs/%<id>s" },
|
|
35
|
+
create: { method: :post, path: "configs/create" },
|
|
36
|
+
destroy: { method: :delete, path: "configs/%<id>s" }
|
|
37
|
+
},
|
|
38
|
+
secrets: {
|
|
39
|
+
index: { method: :get, path: "secrets" },
|
|
40
|
+
show: { method: :get, path: "secrets/%<id>s" },
|
|
41
|
+
create: { method: :post, path: "secrets/create" },
|
|
42
|
+
destroy: { method: :delete, path: "secrets/%<id>s" }
|
|
43
|
+
},
|
|
44
|
+
networks: {
|
|
45
|
+
index: { method: :get, path: "networks" },
|
|
46
|
+
show: { method: :get, path: "networks/%<id>s" },
|
|
47
|
+
create: { method: :post, path: "networks/create" },
|
|
48
|
+
update: { method: :post, path: "networks/%<id>s/update" },
|
|
49
|
+
destroy: { method: :delete, path: "networks/%<id>s" }
|
|
50
|
+
},
|
|
51
|
+
volumes: {
|
|
52
|
+
index: { method: :get, path: "volumes" },
|
|
53
|
+
show: { method: :get, path: "volumes/%<id>s" },
|
|
54
|
+
create: { method: :post, path: "volumes/create" },
|
|
55
|
+
destroy: { method: :delete, path: "volumes/%<id>s" }
|
|
56
|
+
},
|
|
57
|
+
containers: {
|
|
58
|
+
index: { method: :get, path: "containers/json" },
|
|
59
|
+
show: { method: :get, path: "containers/%<id>s/json" },
|
|
60
|
+
create: { method: :post, path: "containers/create" },
|
|
61
|
+
start: { method: :post, path: "containers/%<id>s/start" },
|
|
62
|
+
stop: { method: :post, path: "containers/%<id>s/stop" },
|
|
63
|
+
destroy: { method: :delete, path: "containers/%<id>s" },
|
|
64
|
+
logs: { method: :get, path: "containers/%<id>s/logs" }
|
|
65
|
+
},
|
|
66
|
+
images: {
|
|
67
|
+
index: { method: :get, path: "images/json" },
|
|
68
|
+
show: { method: :get, path: "images/%<id>s/json" },
|
|
69
|
+
create: { method: :post, path: "images/create?fromImage=%<id>s" },
|
|
70
|
+
destroy: { method: :delete, path: "images/%<id>s" }
|
|
71
|
+
}
|
|
72
|
+
}.freeze
|
|
73
|
+
|
|
74
|
+
def self.request(action:, arguments: {}, query_params: {}, payload: nil)
|
|
75
|
+
path = format(action[:path], arguments)
|
|
76
|
+
DockerSwarm.request(
|
|
77
|
+
method: action[:method],
|
|
78
|
+
path: path,
|
|
79
|
+
query: query_params,
|
|
80
|
+
body: payload
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Base
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def resource_name
|
|
9
|
+
name.demodulize.downcase.pluralize
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def routes
|
|
13
|
+
Api::ENDPOINTS[resource_name.to_sym]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def all(filters = {})
|
|
17
|
+
_fetch_all(filters).map { |data| new(data) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find(id)
|
|
21
|
+
data = Api.request(action: routes[:show], arguments: { id: id })
|
|
22
|
+
new(data)
|
|
23
|
+
rescue Errors::NotFound
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def where(filters)
|
|
28
|
+
all(filters)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def _fetch_all(filters = {})
|
|
34
|
+
query = {}
|
|
35
|
+
|
|
36
|
+
if filters.present?
|
|
37
|
+
global_params = filters.slice(:all, :force, :limit, :since, :before)
|
|
38
|
+
docker_filters = filters.except(:all, :force, :limit, :since, :before)
|
|
39
|
+
|
|
40
|
+
query = global_params
|
|
41
|
+
|
|
42
|
+
if docker_filters.any?
|
|
43
|
+
normalized = docker_filters.transform_keys { |k| k.to_s.downcase }
|
|
44
|
+
.transform_values { |v| Array(v) }
|
|
45
|
+
query[:filters] = normalized.to_json
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Api.request(action: routes[:index], query_params: query)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(attributes = {})
|
|
54
|
+
assign_attributes(attributes) if attributes.present?
|
|
55
|
+
super()
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def assign_attributes(new_attributes)
|
|
59
|
+
return if new_attributes.blank?
|
|
60
|
+
|
|
61
|
+
attributes_to_assign = new_attributes.deep_dup
|
|
62
|
+
|
|
63
|
+
if attributes_to_assign.is_a?(Hash)
|
|
64
|
+
attributes_to_assign = attributes_to_assign.with_indifferent_access
|
|
65
|
+
normalized_attributes = {}
|
|
66
|
+
|
|
67
|
+
attributes_to_assign.each do |key, value|
|
|
68
|
+
normalized_key = key.to_s == "Id" ? "ID" : key.to_s
|
|
69
|
+
_define_dynamic_accessor(normalized_key)
|
|
70
|
+
|
|
71
|
+
if normalized_key == "Spec" && respond_to?(:Spec) && self.Spec.is_a?(Hash) && value.is_a?(Hash)
|
|
72
|
+
value = self.Spec.deep_merge(value)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
normalized_attributes[normalized_key] = value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
super(normalized_attributes)
|
|
79
|
+
else
|
|
80
|
+
Array(attributes_to_assign).each do |item|
|
|
81
|
+
next unless item.is_a?(Hash)
|
|
82
|
+
item.each_key { |key| _define_dynamic_accessor(key) }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def attributes
|
|
88
|
+
instance_values.except("validation_context", "errors", "context_for_validation")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def serializable_hash(_options = nil)
|
|
92
|
+
attributes
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def as_json(options = nil)
|
|
96
|
+
serializable_hash(options)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def payload_for_docker
|
|
100
|
+
data = attributes.except("ID", "Id", "Version", "CreatedAt", "UpdatedAt").compact
|
|
101
|
+
return data unless data.key?("Spec")
|
|
102
|
+
|
|
103
|
+
spec = data.delete("Spec").deep_dup
|
|
104
|
+
spec.merge!(data)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def persisted?
|
|
108
|
+
respond_to?(:ID) && self.ID.present?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def id
|
|
112
|
+
self.ID
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def reload
|
|
116
|
+
fresh = self.class.find(id)
|
|
117
|
+
assign_attributes(fresh.attributes) if fresh
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def method_missing(method_name, *args, &block)
|
|
122
|
+
method_str = method_name.to_s
|
|
123
|
+
|
|
124
|
+
if method_str.end_with?("=")
|
|
125
|
+
_define_dynamic_accessor(method_str.chomp("="))
|
|
126
|
+
instance_variable_set("@#{method_str.chomp('=')}", args.first)
|
|
127
|
+
elsif _valid_attribute_name?(method_str) && instance_variable_defined?("@#{method_str}")
|
|
128
|
+
instance_variable_get("@#{method_str}")
|
|
129
|
+
elsif !method_str.end_with?("?")
|
|
130
|
+
_define_dynamic_accessor(method_str)
|
|
131
|
+
nil
|
|
132
|
+
else
|
|
133
|
+
super
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
138
|
+
method_str = method_name.to_s
|
|
139
|
+
method_str.end_with?("=") ||
|
|
140
|
+
(_valid_attribute_name?(method_str) && instance_variable_defined?("@#{method_str}")) ||
|
|
141
|
+
(!method_str.end_with?("?") && _valid_attribute_name?(method_str)) ||
|
|
142
|
+
super
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def _define_dynamic_accessor(key)
|
|
148
|
+
return if self.class.method_defined?("#{key}=")
|
|
149
|
+
self.class.send(:attr_accessor, key)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def _valid_attribute_name?(name)
|
|
153
|
+
/\A[a-zA-Z_]\w*\z/.match?(name)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Concerns
|
|
5
|
+
module Creatable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def create(attributes = {})
|
|
10
|
+
resource = new(attributes)
|
|
11
|
+
resource.save
|
|
12
|
+
resource
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def save
|
|
17
|
+
return false unless valid?
|
|
18
|
+
return update if persisted?
|
|
19
|
+
|
|
20
|
+
response = Api.request(
|
|
21
|
+
action: self.class.routes[:create],
|
|
22
|
+
payload: payload_for_docker
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
self.ID = response["ID"] || response["Id"] || response["Name"]
|
|
26
|
+
reload
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Concerns
|
|
5
|
+
module Deletable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def destroy(id)
|
|
10
|
+
Api.request(action: routes[:destroy], arguments: { id: id })
|
|
11
|
+
true
|
|
12
|
+
rescue Errors::NotFound
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def destroy
|
|
18
|
+
self.class.destroy(self.ID)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Concerns
|
|
5
|
+
module Updatable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def update(new_attributes = {})
|
|
9
|
+
assign_attributes(new_attributes) if new_attributes.present?
|
|
10
|
+
return false unless valid?
|
|
11
|
+
|
|
12
|
+
Api.request(
|
|
13
|
+
action: self.class.routes[:update],
|
|
14
|
+
arguments: { id: self.ID },
|
|
15
|
+
query_params: { version: self.Version&.dig("Index") },
|
|
16
|
+
payload: payload_for_docker
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Connection
|
|
5
|
+
attr_reader :socket_path, :logger
|
|
6
|
+
|
|
7
|
+
def initialize(socket_path, logger)
|
|
8
|
+
@socket_path = socket_path
|
|
9
|
+
@logger = logger
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def request(options = {})
|
|
13
|
+
normalized_path = socket_path.sub(/^unix:\/\//, "")
|
|
14
|
+
client(normalized_path).request(options).body
|
|
15
|
+
rescue => e
|
|
16
|
+
if e.is_a?(Excon::Error::Socket)
|
|
17
|
+
raise Errors::Communication, "Docker socket error: #{e.message}"
|
|
18
|
+
else
|
|
19
|
+
raise e
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def client(path)
|
|
25
|
+
@client ||= Excon.new(
|
|
26
|
+
"unix:///",
|
|
27
|
+
socket: path,
|
|
28
|
+
middlewares: [
|
|
29
|
+
Excon::Middleware::ResponseParser,
|
|
30
|
+
Excon::Middleware::RedirectFollower,
|
|
31
|
+
Middleware::RequestEncoder,
|
|
32
|
+
Middleware::ResponseJSONParser,
|
|
33
|
+
Middleware::ErrorHandler
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
module Errors
|
|
7
|
+
class BadRequest < Error; end
|
|
8
|
+
class Unauthorized < Error; end
|
|
9
|
+
class Forbidden < Error; end
|
|
10
|
+
class NotFound < Error; end
|
|
11
|
+
class NotAcceptable < Error; end
|
|
12
|
+
class RequestTimeout < Error; end
|
|
13
|
+
class Conflict < Error; end
|
|
14
|
+
class UnprocessableEntity < Error; end
|
|
15
|
+
class InternalServerError < Error; end
|
|
16
|
+
class BadGateway < Error; end
|
|
17
|
+
class ServiceUnavailable < Error; end
|
|
18
|
+
class GatewayTimeout < Error; end
|
|
19
|
+
class TooManyRequests < Error; end
|
|
20
|
+
class Communication < Error; end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Middleware
|
|
5
|
+
class ErrorHandler < Excon::Middleware::Base
|
|
6
|
+
def response_call(env)
|
|
7
|
+
return @stack.response_call(env) unless env[:response]
|
|
8
|
+
|
|
9
|
+
status = env[:response][:status]
|
|
10
|
+
body = env[:response][:body]
|
|
11
|
+
|
|
12
|
+
case status
|
|
13
|
+
when 200..299
|
|
14
|
+
# Continuar normalmente
|
|
15
|
+
when 400 then raise Errors::BadRequest, error_message(body)
|
|
16
|
+
when 401 then raise Errors::Unauthorized, error_message(body)
|
|
17
|
+
when 403 then raise Errors::Forbidden, error_message(body)
|
|
18
|
+
when 404 then raise Errors::NotFound, error_message(body)
|
|
19
|
+
when 406 then raise Errors::NotAcceptable, error_message(body)
|
|
20
|
+
when 408 then raise Errors::RequestTimeout, error_message(body)
|
|
21
|
+
when 409 then raise Errors::Conflict, error_message(body)
|
|
22
|
+
when 422 then raise Errors::UnprocessableEntity, body
|
|
23
|
+
when 429 then raise Errors::TooManyRequests, error_message(body)
|
|
24
|
+
when 500 then raise Errors::InternalServerError, error_message(body)
|
|
25
|
+
when 502 then raise Errors::BadGateway, error_message(body)
|
|
26
|
+
when 503 then raise Errors::ServiceUnavailable, error_message(body)
|
|
27
|
+
when 504 then raise Errors::GatewayTimeout, error_message(body)
|
|
28
|
+
else
|
|
29
|
+
raise Errors::Error, "HTTP #{status}: #{error_message(body)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@stack.response_call(env)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def error_message(body)
|
|
38
|
+
if body.is_a?(Hash)
|
|
39
|
+
body["message"] || body["error"] || body.to_json
|
|
40
|
+
else
|
|
41
|
+
body.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Middleware
|
|
5
|
+
class RequestEncoder < Excon::Middleware::Base
|
|
6
|
+
def request_call(env)
|
|
7
|
+
if env[:body] && env[:body].is_a?(Hash)
|
|
8
|
+
env[:body] = env[:body].to_json
|
|
9
|
+
env[:headers]["Content-Type"] ||= "application/json"
|
|
10
|
+
end
|
|
11
|
+
@stack.request_call(env)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
module Middleware
|
|
5
|
+
class ResponseJSONParser < Excon::Middleware::Base
|
|
6
|
+
def response_call(env)
|
|
7
|
+
if env[:response]
|
|
8
|
+
body = env[:response][:body]
|
|
9
|
+
headers = env[:response][:headers] || {}
|
|
10
|
+
|
|
11
|
+
if body && !body.empty? && headers["Content-Type"]&.include?("application/json")
|
|
12
|
+
begin
|
|
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
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@stack.response_call(env)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Container < Base
|
|
5
|
+
include Concerns::Deletable
|
|
6
|
+
|
|
7
|
+
def start
|
|
8
|
+
Api.request(action: self.class.routes[:start], arguments: { id: self.ID })
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def stop
|
|
12
|
+
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)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Network < Base
|
|
5
|
+
include Concerns::Creatable
|
|
6
|
+
include Concerns::Updatable
|
|
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
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
# Represents a Docker Swarm Service
|
|
5
|
+
# @see https://docs.docker.com/engine/api/v1.41/#tag/Service
|
|
6
|
+
class Service < Base
|
|
7
|
+
include Concerns::Creatable
|
|
8
|
+
include Concerns::Updatable
|
|
9
|
+
include Concerns::Deletable
|
|
10
|
+
|
|
11
|
+
validate :validate_name_presence
|
|
12
|
+
|
|
13
|
+
# Fetches logs for the service
|
|
14
|
+
# @param query_params [Hash] Query parameters for the logs endpoint (stdout, stderr, follow, etc.)
|
|
15
|
+
# @return [String] The raw log stream
|
|
16
|
+
def logs(query_params = { stdout: 1, stderr: 1 })
|
|
17
|
+
Api.request(action: self.class.routes[:logs], arguments: { id: self.ID }, query_params: query_params)
|
|
18
|
+
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
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class System < Base
|
|
5
|
+
def self.resource_name
|
|
6
|
+
"system"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.info
|
|
10
|
+
Api.request(action: routes[:info])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.version
|
|
14
|
+
Api.request(action: routes[:version])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.up
|
|
18
|
+
Api.request(action: routes[:up])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.df
|
|
22
|
+
Api.request(action: routes[:df])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DockerSwarm
|
|
4
|
+
class Volume < Base
|
|
5
|
+
include Concerns::Creatable
|
|
6
|
+
include Concerns::Deletable
|
|
7
|
+
|
|
8
|
+
def self.all(filters = {})
|
|
9
|
+
response = _fetch_all(filters)
|
|
10
|
+
return [] if response.blank?
|
|
11
|
+
|
|
12
|
+
data = response.is_a?(Hash) ? response["Volumes"] : response
|
|
13
|
+
Array(data).map { |item| new(item) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/docker_swarm.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/core_ext"
|
|
5
|
+
require "active_model"
|
|
6
|
+
require "excon"
|
|
7
|
+
require "json"
|
|
8
|
+
require "logger"
|
|
9
|
+
|
|
10
|
+
require_relative "docker_swarm/version"
|
|
11
|
+
require_relative "docker_swarm/errors"
|
|
12
|
+
require_relative "docker_swarm/middleware/request_encoder"
|
|
13
|
+
require_relative "docker_swarm/middleware/response_json_parser"
|
|
14
|
+
require_relative "docker_swarm/middleware/error_handler"
|
|
15
|
+
require_relative "docker_swarm/connection"
|
|
16
|
+
require_relative "docker_swarm/api"
|
|
17
|
+
require_relative "docker_swarm/base"
|
|
18
|
+
require_relative "docker_swarm/concerns/creatable"
|
|
19
|
+
require_relative "docker_swarm/concerns/updatable"
|
|
20
|
+
require_relative "docker_swarm/concerns/deletable"
|
|
21
|
+
|
|
22
|
+
# Models
|
|
23
|
+
require_relative "docker_swarm/models/swarm"
|
|
24
|
+
require_relative "docker_swarm/models/system"
|
|
25
|
+
require_relative "docker_swarm/models/service"
|
|
26
|
+
require_relative "docker_swarm/models/node"
|
|
27
|
+
require_relative "docker_swarm/models/task"
|
|
28
|
+
require_relative "docker_swarm/models/container"
|
|
29
|
+
require_relative "docker_swarm/models/image"
|
|
30
|
+
require_relative "docker_swarm/models/network"
|
|
31
|
+
require_relative "docker_swarm/models/config"
|
|
32
|
+
require_relative "docker_swarm/models/secret"
|
|
33
|
+
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
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: docker-swarm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gabriel
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activemodel
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '6.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '6.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: excon
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.80'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.80'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '13.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '13.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
83
|
+
description: Simplifies interactions with Docker Swarm through an ActiveModel-compatible
|
|
84
|
+
ORM and a robust Excon-based API client.
|
|
85
|
+
email:
|
|
86
|
+
- gabriel@wispro.co
|
|
87
|
+
executables: []
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- README.md
|
|
92
|
+
- lib/docker_swarm.rb
|
|
93
|
+
- lib/docker_swarm/api.rb
|
|
94
|
+
- lib/docker_swarm/base.rb
|
|
95
|
+
- lib/docker_swarm/concerns/creatable.rb
|
|
96
|
+
- lib/docker_swarm/concerns/deletable.rb
|
|
97
|
+
- lib/docker_swarm/concerns/updatable.rb
|
|
98
|
+
- lib/docker_swarm/connection.rb
|
|
99
|
+
- lib/docker_swarm/errors.rb
|
|
100
|
+
- lib/docker_swarm/middleware/error_handler.rb
|
|
101
|
+
- lib/docker_swarm/middleware/request_encoder.rb
|
|
102
|
+
- lib/docker_swarm/middleware/response_json_parser.rb
|
|
103
|
+
- lib/docker_swarm/models/config.rb
|
|
104
|
+
- lib/docker_swarm/models/container.rb
|
|
105
|
+
- lib/docker_swarm/models/image.rb
|
|
106
|
+
- lib/docker_swarm/models/network.rb
|
|
107
|
+
- lib/docker_swarm/models/node.rb
|
|
108
|
+
- lib/docker_swarm/models/secret.rb
|
|
109
|
+
- lib/docker_swarm/models/service.rb
|
|
110
|
+
- lib/docker_swarm/models/swarm.rb
|
|
111
|
+
- lib/docker_swarm/models/system.rb
|
|
112
|
+
- lib/docker_swarm/models/task.rb
|
|
113
|
+
- lib/docker_swarm/models/volume.rb
|
|
114
|
+
- lib/docker_swarm/version.rb
|
|
115
|
+
homepage: https://github.com/wispro/docker-swarm
|
|
116
|
+
licenses:
|
|
117
|
+
- MIT
|
|
118
|
+
metadata:
|
|
119
|
+
homepage_uri: https://github.com/wispro/docker-swarm
|
|
120
|
+
source_code_uri: https://github.com/wispro/docker-swarm
|
|
121
|
+
post_install_message:
|
|
122
|
+
rdoc_options: []
|
|
123
|
+
require_paths:
|
|
124
|
+
- lib
|
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: 2.7.0
|
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
requirements: []
|
|
136
|
+
rubygems_version: 3.4.19
|
|
137
|
+
signing_key:
|
|
138
|
+
specification_version: 4
|
|
139
|
+
summary: A Ruby ORM and API client for Docker Swarm.
|
|
140
|
+
test_files: []
|