Class: Chef::Provisioning::DockerDriver::Driver

Inherits:
Chef::Provisioning::Driver
  • Object
show all
Includes:
Mixin::ShellOut
Defined in:
lib/chef/provisioning/docker_driver/driver.rb

Overview

Provisions machines using Docker

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver_url, config) ⇒ Driver

Returns a new instance of Driver.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 31

def initialize(driver_url, config)
  super
  url = Driver.connection_url(driver_url)

  if url
    # Export this as it's expected
    # to be set for command-line utilities
    ENV['DOCKER_HOST'] = url
    Chef::Log.debug("Setting Docker URL to #{url}")
    Docker.url = url
  end

  @connection = Docker.connection
end

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



22
23
24
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 22

def connection
  @connection
end

Class Method Details

.canonicalize_url(driver_url, config) ⇒ Object



46
47
48
49
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 46

def self.canonicalize_url(driver_url, config)
  url = Driver.connection_url(driver_url)
  [ "docker:#{url}", config ]
end

.connection_url(driver_url) ⇒ Object

Parse the url from a URL, try to clean it up Returns a proper URL from the driver_url string. Examples include:

docker:/var/run/docker.sock => unix:///var/run/docker.sock
docker:192.168.0.1:1234 => tcp://192.168.0.1:1234


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 55

def self.connection_url(driver_url)
  scheme, url = driver_url.split(':', 2)

  if url && url.size > 0
    # Clean up the URL with the protocol if needed (within reason)
    case url
    when /^\d+\.\d+\.\d+\.\d+:\d+$/
      "tcp://#{url}"
    when /^\//
      "unix://#{url}"
    when /^(tcp|unix)/
      url
    else
      "tcp://#{url}"
    end
  end
end

.from_url(driver_url, config) ⇒ Object

URL scheme: docker:<path> canonical URL calls realpath on <path>



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

def self.from_url(driver_url, config)
  Driver.new(driver_url, config)
end

Instance Method Details

#allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 135

def allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options)
  # Set machine options on the image to match our newly created image
  image_spec.reference = {
    'driver_url' => driver_url,
    'driver_version' => Chef::Provisioning::DockerDriver::VERSION,
    'allocated_at' => Time.now.to_i,
    :docker_options => {
      :base_image => {
        :name => "chef_#{image_spec.name}",
        :repository => 'chef',
        :tag => image_spec.name
      },
      :from_image => true
    }
  }
  # Workaround for chef/chef-provisioning-docker#37
  machine_spec.attrs[:keep_image] = true
end

#allocate_machine(action_handler, machine_spec, machine_options) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 74

def allocate_machine(action_handler, machine_spec, machine_options)

  container_name = machine_spec.name
  machine_spec.reference = {
      'driver_url' => driver_url,
      'driver_version' => Chef::Provisioning::DockerDriver::VERSION,
      'allocated_at' => Time.now.utc.to_s,
      'host_node' => action_handler.host_node,
      'container_name' => container_name,
      'image_id' => machine_options[:image_id],
      'docker_options' => machine_options[:docker_options]
  }
end

#base_image_for(machine_spec) ⇒ Object



264
265
266
267
268
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 264

def base_image_for(machine_spec)
  Chef::Log.debug("Looking for image #{machine_spec.from_image}")
  image_spec = machine_spec.managed_entry_store.get!(:machine_image, machine_spec.from_image)
  Mash.new(image_spec.reference)[:docker_options][:base_image]
end

#build_container(machine_spec, machine_options) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 94

def build_container(machine_spec, machine_options)
  docker_options = machine_options[:docker_options]

  base_image = docker_options[:base_image]
  if !base_image
    Chef::Log.debug("No base images specified in docker options.")
    base_image = base_image_for(machine_spec)
  end
  source_name = base_image[:name]
  source_repository = base_image[:repository]
  source_tag = base_image[:tag]

  # Don't do this if we're loading from an image
  if docker_options[:from_image]
    "#{source_repository}:#{source_tag}"
  else
    target_repository = 'chef'
    target_tag = machine_spec.name

    # check if target image exists, if not try to look up for source image.
    image = find_image(target_repository, target_tag) || find_image(source_repository, source_tag)

    # kick off image creation
    if image == nil
      Chef::Log.debug("No matching images for #{target_repository}:#{target_tag}, creating!")
      image = Docker::Image.create('fromImage' => source_name,
                                   'repo' => source_repository ,
                                   'tag' => source_tag)
      Chef::Log.debug("Allocated #{image}")
      image.tag('repo' => 'chef', 'tag' => target_tag)
      Chef::Log.debug("Tagged image #{image}")
    elsif not image.info['RepoTags'].include? "#{target_repository}:#{target_tag}"
      # if `find_image(source_repository, source_tag)` returned result, assign target tag to it to be able
      # find it in `start_machine`.
      image.tag('repo' => target_repository, 'tag' => target_tag)
    end

    "#{target_repository}:#{target_tag}"
  end
end

#connect_to_machine(machine_spec, machine_options) ⇒ Object

Connect to machine without acquiring it



165
166
167
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 165

def connect_to_machine(machine_spec, machine_options)
  Chef::Log.debug('Connect to machine!')
end

#convergence_strategy_for(machine_spec, machine_options) ⇒ Object



257
258
259
260
261
262
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 257

def convergence_strategy_for(machine_spec, machine_options)
  @unix_convergence_strategy ||= begin
    Chef::Provisioning::ConvergenceStrategy::InstallCached.
        new(machine_options[:convergence_options], config)
  end
end

#destroy_image(action_handler, image_spec, image_options, machine_options = {}) ⇒ Object



159
160
161
162
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 159

def destroy_image(action_handler, image_spec, image_options, machine_options={})
  image = Docker::Image.get("chef:#{image_spec.name}")
  image.delete unless image.nil?
end

#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 169

def destroy_machine(action_handler, machine_spec, machine_options)
  container_name = machine_spec.location['container_name']
  Chef::Log.debug("Destroying container: #{container_name}")
  container = Docker::Container.get(container_name, @connection)

  begin
    Chef::Log.debug("Stopping #{container_name}")
    container.stop
  rescue Excon::Errors::NotModified
    # this is okay
    Chef::Log.debug('Already stopped!')
  end

  Chef::Log.debug("Removing #{container_name}")
  container.delete

  if !machine_spec.attrs[:keep_image] && !machine_options[:keep_image]
    Chef::Log.debug("Destroying image: chef:#{container_name}")
    image = Docker::Image.get("chef:#{container_name}")
    image.delete
  end
end

#driver_urlObject



208
209
210
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 208

def driver_url
  "docker:#{Docker.url}"
end

#find_image(repository, tag) ⇒ Object



202
203
204
205
206
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 202

def find_image(repository, tag)
  Docker::Image.all.select {
      |i| i.info['RepoTags'].include? "#{repository}:#{tag}"
  }.first
end

#image_named(image_name) ⇒ Object



196
197
198
199
200
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 196

def image_named(image_name)
  Docker::Image.all.select {
      |i| i.info['RepoTags'].include? image_name
  }.first
end

#machine_for(machine_spec, machine_options, base_image_name) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 229

def machine_for(machine_spec, machine_options, base_image_name)
  Chef::Log.debug('machine_for...')

  docker_options = machine_options[:docker_options]

  transport = DockerTransport.new(machine_spec.location['container_name'],
                                  base_image_name,
                                  nil,
                                  Docker.connection)

  convergence_strategy = if docker_options[:from_image]
                           Chef::Provisioning::ConvergenceStrategy::NoConverge.new({}, config)
                         else
                           convergence_strategy_for(machine_spec, machine_options)
                         end

    Chef::Provisioning::DockerDriver::DockerContainerMachine.new(
      machine_spec,
      transport,
      convergence_strategy,
      :command => docker_options[:command],
      :env => docker_options[:env],
      :ports => Array(docker_options[:ports]),
      :volumes => Array(docker_options[:volumes]),
      :keep_stdin_open => docker_options[:keep_stdin_open]
    )
end

#ready_image(action_handler, image_spec, image_options) ⇒ Object



154
155
156
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 154

def ready_image(action_handler, image_spec, image_options)
  Chef::Log.debug('READY IMAGE!')
end

#ready_machine(action_handler, machine_spec, machine_options) ⇒ Object



88
89
90
91
92
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 88

def ready_machine(action_handler, machine_spec, machine_options)
  base_image_name = build_container(machine_spec, machine_options)
  start_machine(action_handler, machine_spec, machine_options, base_image_name)
  machine_for(machine_spec, machine_options, base_image_name)
end

#start_machine(action_handler, machine_spec, machine_options, base_image_name) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 212

def start_machine(action_handler, machine_spec, machine_options, base_image_name)
  # Spin up a docker instance if needed, otherwise use the existing one
  container_name = machine_spec.location['container_name']

  begin
    Docker::Container.get(container_name, @connection)
  rescue Docker::Error::NotFoundError
    docker_options = machine_options[:docker_options]
    Chef::Log.debug("Start machine for container #{container_name} using base image #{base_image_name} with options #{docker_options.inspect}")
    image = image_named(base_image_name)
    container = Docker::Container.create('Image' => image.id, 'name' => container_name)
    Chef::Log.debug("Container id: #{container.id}")
    machine_spec.location['container_id'] = container.id
  end

end

#stop_machine(action_handler, node) ⇒ Object



192
193
194
# File 'lib/chef/provisioning/docker_driver/driver.rb', line 192

def stop_machine(action_handler, node)
  Chef::Log.debug("Stop machine: #{node.inspect}")
end