Module: Docker::API::Context

Defined in:
lib/docker/api/context.rb

Overview

Docker's context store, which is where the CLI keeps the daemon it is pointed at when DOCKER_HOST is not set.

docker context use colima does not export anything. It writes currentContext into ~/.docker/config.json, and every later docker command resolves the endpoint out of the store. A client that reads only DOCKER_HOST therefore disagrees with the CLI on the same machine, and falls back to /var/run/docker.sock -- which Docker Desktop happens to symlink, and Colima, Rancher Desktop, rootless Docker and Podman generally do not. The failure is a connection refused with a correct looking environment, which is a bad way to spend an afternoon.

The store is a directory per context, named for the SHA-256 of the context's own name, holding a meta.json:

~/.docker/contexts/meta/<sha256(name)>/meta.json
~/.docker/contexts/tls/<sha256(name)>/docker/{ca,cert,key}.pem

Every failure here is soft. An unreadable store, a malformed meta.json or a context that no longer exists all mean "no context", because falling back to the platform default is what the CLI does and is more useful than refusing to start.

Constant Summary collapse

DEFAULT =

The context name that means "no context": use DOCKER_HOST, or the platform's default socket. It has no entry in the store.

"default"

Class Method Summary collapse

Class Method Details

.config_root(env = ENV) ⇒ String

Returns the Docker configuration directory.

Parameters:

  • env (Hash) (defaults to: ENV)

Returns:

  • (String)

    the Docker configuration directory



84
85
86
87
88
89
90
91
92
93
# File 'lib/docker/api/context.rb', line 84

def config_root(env = ENV)
  configured = env["DOCKER_CONFIG"]
  return configured unless configured.nil? || configured.empty?

  File.join(Dir.home, ".docker")
rescue ArgumentError
  # Dir.home raises when there is no home to speak of, which happens in
  # stripped containers and some CI images.
  ".docker"
end

.current_name(env = ENV, root = config_root(env)) ⇒ String?

The active context's name: DOCKER_CONTEXT first, then whatever docker context use last wrote into config.json.

Parameters:

  • env (Hash) (defaults to: ENV)
  • root (String) (defaults to: config_root(env))

Returns:

  • (String, nil)


59
60
61
62
63
64
65
# File 'lib/docker/api/context.rb', line 59

def current_name(env = ENV, root = config_root(env))
  from_env = env["DOCKER_CONTEXT"]
  return from_env unless from_env.nil? || from_env.empty?

  config = read_json(File.join(root, "config.json"))
  config.is_a?(Hash) ? config["currentContext"] : nil
end

.endpoint(name, root = config_root) ⇒ Hash?

Returns with :url and optionally :tls.

Parameters:

  • name (String)

    a context name

  • root (String) (defaults to: config_root)

    the Docker configuration directory

Returns:

  • (Hash, nil)

    with :url and optionally :tls



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/docker/api/context.rb', line 70

def endpoint(name, root = config_root)
  digest = Digest::SHA256.hexdigest(name)
  meta = read_json(File.join(root, "contexts", "meta", digest, "meta.json"))
  host = meta.dig("Endpoints", "docker", "Host") if meta.is_a?(Hash)
  return nil if host.nil? || host.empty?

  resolved = { url: host }
  tls = tls_material(root, digest, skip_verify: meta.dig("Endpoints", "docker", "SkipTLSVerify"))
  resolved[:tls] = tls unless tls.empty?
  resolved
end

.read_json(path) ⇒ Object?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Object, nil)


115
116
117
118
119
120
121
# File 'lib/docker/api/context.rb', line 115

def read_json(path)
  return nil unless File.readable?(path)

  JSON.parse(File.read(path))
rescue JSON::ParserError, SystemCallError
  nil
end

.resolve(env = ENV, root: nil) ⇒ Hash?

Resolve the endpoint the CLI would use, if any.

Parameters:

  • env (Hash) (defaults to: ENV)

    the environment to read

  • root (String, nil) (defaults to: nil)

    the Docker configuration directory

Returns:

  • (Hash, nil)

    with :url and optionally :tls, or nil when no context applies



45
46
47
48
49
50
51
# File 'lib/docker/api/context.rb', line 45

def resolve(env = ENV, root: nil)
  root ||= config_root(env)
  name = current_name(env, root)
  return nil if name.nil? || name.empty? || name == DEFAULT

  endpoint(name, root)
end

.tls_material(root, digest, skip_verify: false) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A context may carry its own TLS material, which is how docker context create --docker host=tcp://...,ca=...,cert=...,key=... stores it.

Returns:

  • (Hash)


100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/docker/api/context.rb', line 100

def tls_material(root, digest, skip_verify: false)
  directory = File.join(root, "contexts", "tls", digest, "docker")
  material = { ca_file: "ca.pem", cert_file: "cert.pem", key_file: "key.pem" }
    .filter_map { |key, file|
      path = File.join(directory, file)
      [key, path] if File.readable?(path)
    }.to_h

  return {} if material.empty?

  material.merge(verify: !skip_verify)
end