Class: Chef::Provisioning::DockerDriver::DockerTransport

Inherits:
Transport
  • Object
show all
Includes:
Mixin::ShellOut
Defined in:
lib/chef/provisioning/docker_driver/docker_transport.rb

Defined Under Namespace

Classes: DockerResult

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(container_name, base_image_name, credentials, connection, tunnel_transport = nil) ⇒ DockerTransport

Returns a new instance of DockerTransport.



15
16
17
18
19
20
21
22
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 15

def initialize(container_name, base_image_name, credentials, connection, tunnel_transport = nil)
  @repository_name = 'chef'
  @container_name = container_name
  @image = Docker::Image.get(base_image_name, connection)
  @credentials = credentials
  @connection = connection
  @tunnel_transport = tunnel_transport
end

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



30
31
32
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 30

def connection
  @connection
end

#container_nameObject (readonly)

Returns the value of attribute container_name.



26
27
28
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 26

def container_name
  @container_name
end

#credentialsObject (readonly)

Returns the value of attribute credentials.



29
30
31
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 29

def credentials
  @credentials
end

#imageObject (readonly)

Returns the value of attribute image.



28
29
30
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 28

def image
  @image
end

#repository_nameObject (readonly)

Returns the value of attribute repository_name.



27
28
29
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 27

def repository_name
  @repository_name
end

#tunnel_transportObject (readonly)

Returns the value of attribute tunnel_transport.



31
32
33
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 31

def tunnel_transport
  @tunnel_transport
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


214
215
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 214

def available?
end

#disconnectObject



210
211
212
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 210

def disconnect
  @proxy_thread.kill if @proxy_thread
end

#download_file(path, local_path) ⇒ Object



157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 157

def download_file(path, local_path)
  # TODO stream
  file = File.open(local_path, 'w')
  begin
    file.write(read_file(path))
    file.close
  rescue
    File.delete(file)
  end
end

#execute(command, options = {}) ⇒ Object

Execute the specified command inside the container, returns a Mixlib::Shellout object Options contains the optional keys:

:env => env vars
:read_only => Do not commit this execute operation, just execute it
:ports => ports to listen on (-p command-line options)
:detached => true/false, execute this command in detached mode (for final program to run)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 39

def execute(command, options={})
  Chef::Log.debug("execute '#{command}' with options #{options}")

  begin
    connection.post("/containers/#{container_name}/stop?t=0", '')
    Chef::Log.debug("stopped /containers/#{container_name}")
  rescue Excon::Errors::NotModified
    Chef::Log.debug("Already stopped #{container_name}")
  rescue Docker::Error::NotFoundError
  end

  begin
    # Delete the container if it exists and is dormant
    connection.delete("/containers/#{container_name}?v=true&force=true")
    Chef::Log.debug("deleted /containers/#{container_name}")
  rescue Docker::Error::NotFoundError
  end

  command = Shellwords.split(command) if command.is_a?(String)

  # TODO shell_out has no way to live stream stderr???
  live_stream = nil
  live_stream = STDOUT if options[:stream]
  live_stream = options[:stream_stdout] if options[:stream_stdout]

  args = ['docker', 'run', '--name', container_name]

  if options[:env]      
	options[:env].each do |key, value| 
      args << '-e'
      args << "#{key}=#{value}"
    end
  end

  if options[:detached]
    args << '--detach'
  end

  if options[:ports]
    options[:ports].each do |portnum|
      args << '-p'
      args << "#{portnum}"
    end
  end

  if options[:keep_stdin_open]
    args << '-i'
  end

  args << @image.id
  args += command

  cmdstr = Shellwords.join(args)
  Chef::Log.debug("Executing #{cmdstr}")

  # Remove this when https://github.com/opscode/chef/pull/2100 gets merged and released
  # nullify live_stream because at the moment EventsOutputStream doesn't understand <<, which
  # ShellOut uses
  live_stream = nil unless live_stream.respond_to? :<<

  cmd = Mixlib::ShellOut.new(cmdstr, :live_stream => live_stream, :timeout => execute_timeout(options))

  cmd.run_command

  unless options[:read_only]
    Chef::Log.debug("Committing #{container_name} as #{repository_name}:#{container_name}")
    container = Docker::Container.get(container_name)
    @image = container.commit('repo' => repository_name, 'tag' => container_name)
  end

  Chef::Log.debug("Execute complete: status #{cmd.exitstatus}")

  cmd
end

#make_url_available_to_remote(url) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 172

def make_url_available_to_remote(url)
  # The host is already open to the container.  Just find out its address and return it!
  uri = URI(url)
  host = Socket.getaddrinfo(uri.host, uri.scheme, nil, :STREAM)[0][3]
  Chef::Log.debug("Making URL available: #{host}")

  if host == '127.0.0.1' || host == '[::1]'
    result = execute('ip route ls', :read_only => true)

    Chef::Log.debug("IP route: #{result.stdout}")

    if result.stdout =~ /default via (\S+)/

      uri.host = if using_boot2docker?
                   # Intermediate VM does NAT, so local address should be fine here
                   Chef::Log.debug("Using boot2docker!")
                   IPSocket.getaddress(Socket.gethostname)
                 else
                   $1
                 end

      if !@proxy_thread
        # Listen to docker instances only, and forward to localhost
        @proxy_thread = Thread.new do
          Chef::Log.debug("Starting proxy thread: #{uri.host}:#{uri.port} <--> #{host}:#{uri.port}")
          ChefZeroHttpProxy.new(uri.host, uri.port, host, uri.port).run
        end
      end
      Chef::Log.debug("Using Chef server URL: #{uri.to_s}")

      return uri.to_s
    else
      raise "Cannot forward port: ip route ls did not show default in expected format.\nSTDOUT: #{result.stdout}"
    end
  end
  url
end

#read_file(path) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 114

def read_file(path)
  container = Docker::Container.create({
    'Image' => @image.id,
    'Cmd' => %w(echo true)
  }, connection)
  begin
    tarfile = ''
    # NOTE: this would be more efficient if we made it a stream and passed that to Minitar
    container.copy(path) do |block|
      tarfile << block
    end
  rescue Docker::Error::ServerError
    if $!.message =~ /500/ || $!.message =~ /Could not find the file/
      return nil
    else
      raise
    end
  ensure
    container.delete
  end

  output = ''
  Archive::Tar::Minitar::Input.open(StringIO.new(tarfile)) do |inp|
    inp.each do |entry|
      while next_output = entry.read
        output << next_output
      end
      entry.close
    end
  end

  output
end

#upload_file(local_path, path) ⇒ Object



168
169
170
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 168

def upload_file(local_path, path)
  @image = @image.insert_local('localPath' => local_path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
end

#write_file(path, content) ⇒ Object



148
149
150
151
152
153
154
155
# File 'lib/chef/provisioning/docker_driver/docker_transport.rb', line 148

def write_file(path, content)
  # TODO hate tempfiles.  Find an in memory way.
  Tempfile.open('metal_docker_write_file') do |file|
    file.write(content)
    file.close
    @image = @image.insert_local('localPath' => file.path, 'outputPath' => path, 't' => "#{repository_name}:#{container_name}")
  end
end