Class: DockerSwarm::Container

Inherits:
Base
  • Object
show all
Includes:
DockerSwarm::Concerns::Creatable, DockerSwarm::Concerns::Deletable, DockerSwarm::Concerns::Loggable
Defined in:
lib/docker_swarm/models/container.rb

Overview

Represents a Docker Container

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DockerSwarm::Concerns::Loggable

#logs

Methods included from DockerSwarm::Concerns::Deletable

#destroy

Methods included from DockerSwarm::Concerns::Creatable

#query_params_for_docker, #save

Methods inherited from Base

all, #as_json, #assign_attributes, #attributes, defined_attributes, find, #id, #initialize, #method_missing, #payload_for_docker, #persisted?, #reload, resource_name, #respond_to_missing?, root_key, routes, #serializable_hash, where

Methods included from DockerSwarm::Concerns::Inspectable

#inspect

Constructor Details

This class inherits a constructor from DockerSwarm::Base

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class DockerSwarm::Base

Class Method Details

.create_query_paramsArray<String>

POST /containers/create toma el nombre por query string. En el body Docker lo descarta en silencio y responde 201: el container nace con nombre aleatorio y la adopción por nombre determinista en un reintento no encuentra nada, así que el reintento duplica. Ver ADR-025 cláusula 1.

Returns:

  • (Array<String>)


16
17
18
# File 'lib/docker_swarm/models/container.rb', line 16

def self.create_query_params
  %w[name].freeze
end

.index_query_paramsArray<Symbol>

GET /containers/json declara exactamente tres query params propios además de filters: all, limit y size (spec v1.41, ContainerList).

No hereda el default de Base porque since y before son filtros de este recurso, no query params: ruteados a la URL el Engine los ignora y devuelve la lista sin filtrar, sin error — resultado incorrecto silencioso, peor que el rechazo ruidoso de #22. Y size faltaba: es query param propio (pide el tamaño de los archivos del container), así que viajaba dentro de filters como filtro inválido. Ver #35.

Returns:

  • (Array<Symbol>)

    Symbols (matchean contra las claves de filters)



31
32
33
# File 'lib/docker_swarm/models/container.rb', line 31

def self.index_query_params
  %i[all limit size].freeze
end

Instance Method Details

#restart(timeout: nil) ⇒ Boolean

Reinicia el container (+POST /containers/Base#id/restart+).

⚠️ No se parece a Service#restart, y está bien. El hermano simula el restart incrementando ForceUpdate porque los services de Swarm no tienen endpoint de restart. Los containers sí lo tienen, así que copiar ese workaround sería arrastrar una vuelta que acá no hace falta.

Parameters:

  • timeout (Integer, nil) (defaults to: nil)

    segundos a esperar antes de matar el proceso (+t+ de Docker). nil deja el default del Engine, que no es lo mismo que mandar 0 (eso mataría sin gracia).

Returns:

  • (Boolean)

    true si el Engine aceptó el reinicio



124
125
126
127
128
129
130
131
# File 'lib/docker_swarm/models/container.rb', line 124

def restart(timeout: nil)
  Api.request(
    action: self.class.routes[:restart],
    arguments: { id: self.ID },
    query_params: timeout.nil? ? {} : { t: timeout }
  )
  true
end

#startBoolean

Starts the container

Returns:

  • (Boolean)

    true if successful



37
38
39
40
# File 'lib/docker_swarm/models/container.rb', line 37

def start
  Api.request(action: self.class.routes[:start], arguments: { id: self.ID })
  true
end

#stats(query_params = {}) ⇒ Hash

Métricas de uso del container (+GET /containers/Base#id/stats+).

🚦 stream: false NO es un default cómodo: es lo que hace que el método vuelva. El endpoint streamea por default —+stream=true+— y la conexión queda abierta emitiendo un objeto por segundo. Medido contra un Engine 29.7.2: sin el parámetro, 6 objetos en 6 s y la conexión no cierra (+exit 28+ de curl); con stream=false, un objeto y cierra en 1 s. En una llamada RPC el default cuelga — y el modo de falla no es un error, es una espera. Mismo patrón que ADR-027: el default de Docker no es el que sirve.

Por eso el parámetro se mergea en vez de reemplazarse, que es como lo hace DockerSwarm::Concerns::Loggable#logs: ahí un caller que pasa su propio hash sólo cambia qué streams lee; acá lo dejaría colgado. Se puede pisar a propósito (+stats(stream: true)+), pero no por accidente.

Parameters:

  • query_params (Hash) (defaults to: {})

    parámetros extra (+one-shot+, …). stream va en false salvo que se lo pise explícitamente.

Returns:

  • (Hash)

    snapshot de métricas parseado



150
151
152
153
154
155
156
# File 'lib/docker_swarm/models/container.rb', line 150

def stats(query_params = {})
  Api.request(
    action: self.class.routes[:stats],
    arguments: { id: self.ID },
    query_params: { stream: false }.merge(query_params)
  )
end

#stopBoolean

Stops the container

Returns:

  • (Boolean)

    true if successful



44
45
46
47
# File 'lib/docker_swarm/models/container.rb', line 44

def stop
  Api.request(action: self.class.routes[:stop], arguments: { id: self.ID })
  true
end

#update(new_attributes = {}, **opts) ⇒ Hash

Actualiza los límites de recursos del container (+POST /containers/Base#id/update+).

⚠️ NO usa DockerSwarm::Concerns::Updatable, y no es un olvido. Ese concern está escrito para services de Swarm: manda ?version=<Version.Index> para el control de concurrencia optimista y serializa con payload_for_docker. El update de un container no tiene version — es otro endpoint con otra semántica (cpu, memoria, reinicio) — así que incluirlo mandaría un query param que el Engine ignora en silencio.

🚦 La firma absorbe kwargs a propósito. DockerSwarm::Concerns::Creatable#save llama update(registry_auth:) cuando el objeto ya está persistido; con una firma update(new_attributes = {}) ese kwarg se convierte en Hash posicional (Ruby 3) y terminaría posteado como atributo: {"registry_auth": null} hacia el Engine, sin error. Los dos de registry se descartan acá porque son cosa de Service (header X-Registry-Auth / query registryAuthFrom); un container no autentica en su update.

Devuelve el cuerpo de la respuesta —Docker responde +[...]+— y no un booleano: el Engine avisa por ahí cuando un límite no se pudo aplicar. Colapsarlo a true se comería justamente la señal.

🚦 Un payload vacío levanta, y por eso save sobre un container persistido NO funciona — falla fuerte. DockerSwarm::Concerns::Creatable#save llama update(registry_auth:) sin atributos, así que el payload queda en {} y el Engine responde +200 OK+ sin aplicar nada (medido: body {}{"Warnings":null}, Memory sin cambiar). Dejarlo pasar convertiría a save en una promesa vacía: el caller asigna atributos, llama save, y los pierde sin un solo error.

No se soporta el save genérico a propósito: POST /containers/{id}/update no es "guardar el objeto", es un endpoint angosto de límites de recursos. Derivar el payload de los atributos locales exigiría una whitelist de los campos que Docker acepta, que driftea contra la API. Mejor decirlo que fingirlo.

Parameters:

  • new_attributes (Hash) (defaults to: {})

    límites de recursos en el shape de Docker (+Memory+, NanoCpus, RestartPolicy, …)

  • opts (Hash)

    atributos sueltos; registry_auth y registry_auth_from se descartan

Returns:

  • (Hash)

    cuerpo de la respuesta del Engine (+Warnings+)

Raises:

  • (ArgumentError)

    si no queda ningún atributo para mandar



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/docker_swarm/models/container.rb', line 85

def update(new_attributes = {}, **opts)
  # El `except` va sobre el MERGE, no sólo sobre `opts`: un caller que pase
  # `{registry_auth: "…", Memory: …}` como Hash **posicional** metería la credencial en el
  # payload, y de ahí al log (`body=…`) — la misma fuga que arregló #24 por el otro lado.
  payload = new_attributes.merge(opts).except(:registry_auth, :registry_auth_from,
                                              "registry_auth", "registry_auth_from")

  if payload.empty?
    raise ArgumentError,
          "Container#update requires at least one attribute. An empty payload gets a 200 OK " \
          "from the Engine and applies nothing. Coming from `save`? Containers do not support " \
          "the generic save: use `update(\"Memory\" => ...)` with explicit resource limits."
  end

  response = Api.request(
    action: self.class.routes[:update],
    arguments: { id: self.ID },
    payload: payload
  )

  # El estado local queda stale si no se recarga: el payload es **plano** (`Memory`) pero el
  # objeto lo tiene anidado (`HostConfig.Memory`), así que un `assign_attributes` —lo que
  # hace {Concerns::Updatable}— crearía un atributo fantasma en vez de actualizar el real.
  # `reload` trae la forma correcta del Engine; es el mismo cierre que usa `save` al crear.
  reload
  response
end