Class: Docker::Compose::Mapper

Inherits:
Object
  • Object
show all
Defined in:
lib/docker/compose/mapper.rb

Overview

Uses a Session to discover information about services’ IP addresses and ports as reachable from the host, then

Constant Summary collapse

ELIDED =

Pattern that matches an “elided” host or port that should be omitted from output, but is needed to identify a specific container and port.

/^\[.+\]$/.freeze
REMOVE_ELIDED =

Regexp that can be used with gsub to strip elision marks

/[\[\]]/.freeze
BadSubstitution =
Class.new(StandardError)
NoService =
Class.new(RuntimeError)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session, docker_host, strict: true) ⇒ Mapper

Create an instance of Mapper

Parameters:

  • session (Docker::Compose::Session)
  • docker_host (String)

    DNS hostnrame or IPv4 address of the host that is publishing Docker services (i.e. the ‘DOCKER_HOST` hostname or IP if you are using a non-clustered Docker environment)

  • strict (Boolean) (defaults to: true)

    if true, raise BadSubstitution when unrecognized syntax is passed to #map; if false, simply return unrecognized values without substituting anything



42
43
44
45
46
# File 'lib/docker/compose/mapper.rb', line 42

def initialize(session, docker_host, strict:true)
  @session = session
  @docker_host = docker_host
  @strict  = strict
end

Class Method Details

.map(env, strict: true, session: Session.new, net_info: NetInfo.new) { ... } ⇒ Object

Instantiate a mapper; map some environment variables; yield to caller for additional processing.

Parameters:

  • strict (Boolean) (defaults to: true)
  • session (Session) (defaults to: Session.new)
  • net_info (NetInfo) (defaults to: NetInfo.new)

Yields:

  • yields with each substituted (key, value) pair



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/docker/compose/mapper.rb', line 22

def self.map(env, strict:true, session:Session.new, net_info:NetInfo.new)
  mapper = self.new(session, net_info.docker_routable_ip, strict:strict)
  env.each_pair do |k, v|
    begin
      v = mapper.map(v)
      yield(k, v)
    rescue NoService
      yield(k, nil)
    end
  end
end

Instance Method Details

#map(value) ⇒ Object

Substitute service hostnames and ports that appear in a URL or a host:port string. If either component of a host:port string is surrounded by square brackets, “elide” that component, removing it from the result but using it to find the correct service and port.

Examples:

map MySQL on local docker host with 3306 published to 13847

map("tcp://db:3306") # => "tcp://127.0.0.1:13847"

map just the hostname of MySQL on local docker host

map("db:[3306]") # => "127.0.0.1"

map just the port of MySQL on local docker host

map("[db]:3306") # => "13847"

Parameters:

  • value (String)

    a URI or a host:port pair

Raises:

  • (BadSubstitution)

    if a substitution string can’t be parsed

  • (NoService)

    if service is not up or does not publish port



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/docker/compose/mapper.rb', line 66

def map(value)
  uri = URI.parse(value) rescue nil
  pair = value.split(':')

  if uri && uri.scheme && uri.host
    # absolute URI with scheme, authority, etc
    uri.port = published_port(uri.host, uri.port)
    uri.host = @docker_host
    return uri.to_s
  elsif pair.size == 2
    # "host:port" pair; three sub-cases...
    if pair.first =~ ELIDED
      # output only the port
      service = pair.first.gsub(REMOVE_ELIDED, '')
      port = published_port(service, pair.last)
      return port.to_s
    elsif pair.last =~ ELIDED
      # output only the hostname; resolve the port anyway to ensure that
      # the service is running.
      service = pair.first
      port = pair.last.gsub(REMOVE_ELIDED, '')
      published_port(service, port)
      return @docker_host
    else
      # output port:hostname pair
      port = published_port(pair.first, pair.last)
      return "#{@docker_host}:#{port}"
    end
  elsif @strict
    raise BadSubstitution, "Can't understand '#{value}'"
  else
    return value
  end
end

#published_port(service, port) ⇒ Integer

Figure out which host port a given service’s port has been published to, and/or whether that service is running. Cannot distinguish between the “service not running” case and the “container port not published” case!

Returns:

  • (Integer)

    host port number, or nil if port not published

Raises:

  • (NoService)

    if service is not up or does not publish port



107
108
109
110
111
112
# File 'lib/docker/compose/mapper.rb', line 107

def published_port(service, port)
  result = @session.port(service, port)
  Integer(result.split(':').last.gsub("\n", ""))
rescue RuntimeError
  raise NoService, "Service '#{service}' not running, or does not publish port '#{port}'"
end