Class: Bolt::Transport::Docker::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/transport/docker/connection.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target) ⇒ Connection

Returns a new instance of Connection.



12
13
14
15
16
17
18
19
20
# File 'lib/bolt/transport/docker/connection.rb', line 12

def initialize(target)
  raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
  @target = target
  @user = ENV['USER'] || Etc.getlogin
  @logger = Bolt::Logger.logger(target.safe_name)
  @container_info = {}
  @docker_host = target.options['service-url']
  @logger.trace("Initializing docker connection to #{target.safe_name}")
end

Instance Attribute Details

#targetObject (readonly)

Returns the value of attribute target.



10
11
12
# File 'lib/bolt/transport/docker/connection.rb', line 10

def target
  @target
end

#userObject (readonly)

Returns the value of attribute user.



10
11
12
# File 'lib/bolt/transport/docker/connection.rb', line 10

def user
  @user
end

Instance Method Details

#add_env_vars(env_vars) ⇒ Object



69
70
71
# File 'lib/bolt/transport/docker/connection.rb', line 69

def add_env_vars(env_vars)
  @env_vars = Bolt::Util.format_env_vars_for_cli(env_vars)
end

#connectObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/bolt/transport/docker/connection.rb', line 50

def connect
  # We don't actually have a connection, but we do need to
  # check that the container exists and is running.
  output = execute_local_json_command('ps', ['--no-trunc'])
  index = output.find_index { |item| item["ID"].start_with?(target.host) || item["Names"] == target.host }
  raise "Could not find a container with name or ID matching '#{target.host}'" if index.nil?
  # Now find the indepth container information
  output = execute_local_json_command('inspect', [output[index]["ID"]])
  # Store the container information for later
  @container_info = output[0]
  @logger.trace { "Opened session" }
  true
rescue StandardError => e
  raise Bolt::Node::ConnectError.new(
    "Failed to connect to #{target.safe_name}: #{e.message}",
    'CONNECT_ERROR'
  )
end

#container_idString

The full ID of the target container



37
38
39
# File 'lib/bolt/transport/docker/connection.rb', line 37

def container_id
  @container_info["Id"]
end

#download_file(source, destination, _download) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/bolt/transport/docker/connection.rb', line 108

def download_file(source, destination, _download)
  @logger.trace { "Downloading #{source} to #{destination}" }
  # Create the destination directory, otherwise copying a source directory with Docker will
  # copy the *contents* of the directory.
  # https://docs.docker.com/engine/reference/commandline/cp/
  FileUtils.mkdir_p(destination)
  _out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
  unless stat.exitstatus.zero?
    raise "Error downloading content from container #{container_id}: #{err}"
  end
rescue StandardError => e
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
end

#execute(command) ⇒ Object

Executes a command inside the target container. This is called from the shell class.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/bolt/transport/docker/connection.rb', line 76

def execute(command)
  args = []
  # CODEREVIEW: Is it always safe to pass --interactive?
  args += %w[--interactive]
  args += %w[--tty] if target.options['tty']
  args += @env_vars if @env_vars

  if target.options['shell-command'] && !target.options['shell-command'].empty?
    # escape any double quotes in command
    command = command.gsub('"', '\"')
    command = "#{target.options['shell-command']} \"#{command}\""
  end

  docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
  @logger.trace { "Executing: #{docker_command.join(' ')}" }

  Open3.popen3(env_hash, *docker_command)
rescue StandardError
  @logger.trace { "Command aborted" }
  raise
end

#execute_local_json_command(subcommand, arguments = []) ⇒ Object

Executes a Docker CLI command and parses the output in JSON format



129
130
131
132
133
# File 'lib/bolt/transport/docker/connection.rb', line 129

def execute_local_json_command(subcommand, arguments = [])
  cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
  out, _err, _stat = run_cmd(cmd, env_hash)
  extract_json(out)
end

#reset_cwd?Boolean



30
31
32
# File 'lib/bolt/transport/docker/connection.rb', line 30

def reset_cwd?
  true
end

#run_cmd(cmd, env_vars) ⇒ Object



41
42
43
# File 'lib/bolt/transport/docker/connection.rb', line 41

def run_cmd(cmd, env_vars)
  Bolt::Util.exec_docker(cmd, env_vars)
end

#shellObject



22
23
24
25
26
27
28
# File 'lib/bolt/transport/docker/connection.rb', line 22

def shell
  @shell ||= if Bolt::Util.windows?
               Bolt::Shell::Powershell.new(target, self)
             else
               Bolt::Shell::Bash.new(target, self)
             end
end

#upload_file(source, destination) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/bolt/transport/docker/connection.rb', line 98

def upload_file(source, destination)
  @logger.trace { "Uploading #{source} to #{destination}" }
  _out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
  unless stat.exitstatus.zero?
    raise "Error writing to container #{container_id}: #{err}"
  end
rescue StandardError => e
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
end