Class: Dev::Docker

Inherits:
Object show all
Defined in:
lib/firespring_dev_commands/docker.rb,
lib/firespring_dev_commands/docker/status.rb,
lib/firespring_dev_commands/docker/compose.rb,
lib/firespring_dev_commands/docker/desktop.rb

Overview

Class contains many useful methods for interfacing with the docker api

Defined Under Namespace

Classes: Compose, Config, Desktop, Status

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDocker

Returns a new instance of Docker.



41
42
43
# File 'lib/firespring_dev_commands/docker.rb', line 41

def initialize
  check_version
end

Class Method Details

.config {|@config| ... } ⇒ Object Also known as: configure

Instantiates a new top level config object if one hasn’t already been created Yields that config object to any given block Returns the resulting config object

Yields:



26
27
28
29
30
# File 'lib/firespring_dev_commands/docker.rb', line 26

def config
  @config ||= Config.new
  yield(@config) if block_given?
  @config
end

.versionObject

Returns the version of the docker engine running on the system



36
37
38
# File 'lib/firespring_dev_commands/docker.rb', line 36

def version
  @version ||= JSON.parse(::Docker.connection.get('/version'))['Version']
end

Instance Method Details

#check_versionObject

Checks the min and max version against the current docker version if they have been configured



46
47
48
49
50
51
52
# File 'lib/firespring_dev_commands/docker.rb', line 46

def check_version
  min_version = self.class.config.min_version
  raise "requires docker version >= #{min_version} (found #{self.class.version})" if min_version && !Dev::Common.new.version_greater_than(min_version, self.class.version)

  max_version = self.class.config.max_version
  raise "requires docker version < #{max_version} (found #{self.class.version})" if max_version && Dev::Common.new.version_greater_than(max_version, self.class.version)
end

#container_by_name(service_name, prefix = nil, status: [Docker::Status::RUNNING]) ⇒ Object

Deprecated.

Calls the docker compose method with the given inputs



167
168
169
170
# File 'lib/firespring_dev_commands/docker.rb', line 167

def container_by_name(service_name, prefix = nil, status: [Docker::Status::RUNNING])
  warn '[DEPRECATION] `Docker#container_by_name` is deprecated.  Please use `Docker::Compose#container_by_name` instead.'
  Docker::Compose.new.container_by_name(service_name, prefix, status)
end

#copy_from_container(container, source, destination, required: true) ⇒ Object

Copies the source path on the container to the destination path on your local machine If required is set to true, the command will fail if the source path does not exist on the container



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/firespring_dev_commands/docker.rb', line 225

def copy_from_container(container, source, destination, required: true)
  source = File.join(working_dir(container), source) unless source.start_with?(File::SEPARATOR)
  LOG.info "Copying #{source} to #{destination}..."

  tar = StringIO.new
  begin
    container.archive_out(source) do |chunk|
      tar.write(chunk)
    end
  rescue => e
    raise e if required

    puts 'Not Found'
  end

  Dev::Tar.new(tar).unpack(source, destination)
end

#copy_to_container(container, source, destination) ⇒ Object

Copies the source path on your local machine to the destination path on the container



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/firespring_dev_commands/docker.rb', line 185

def copy_to_container(container, source, destination)
  # Add the working dir of the container onto the destination (if it doesn't start a path separator)
  destination = File.join(working_dir(container), destination) unless destination.start_with?(File::SEPARATOR)
  LOG.info "Copying #{source} to #{destination}..."

  # Need to determine the type of the destination (file or directory or nonexistant)
  noexist_code = 22
  file_code = 33
  directory_code = 44
  unknown_code = 55
  filetype_cmd = [
    'bash',
    '-c',
    "set -e; [ ! -e '#{destination}' ] && exit #{noexist_code}; [ -f '#{destination}' ] " \
    "&& exit #{file_code}; [ -d '#{destination}' ] && exit #{directory_code}; exit #{unknown_code}"
  ]
  destination_filetype_code = container.exec(filetype_cmd).last

  # If destination_filetype_code is a file - that means the user passed in a destination filename
  # Unfortunately the archive_in command does not support that so we will strip it off and use it later (if needed)
  source_filename = File.basename(source)
  destination_filename = File.basename(source)
  destination, _, destination_filename = destination.rpartition(File::SEPARATOR) if destination_filetype_code == file_code

  container.archive_in(source, destination, overwrite: true)

  if File.directory?(source)
    # If the source was a directory, then the archive_in command leaves it as a tar on the system - so we need to unpack it
    # TODO: Can we find a better solution for this? Seems pretty brittle
    retcode = container.exec(['bash', '-c', "cd #{destination}; tar -xf #{destination_filename}; rm -f #{destination_filename}"]).last
    raise 'Unable to unpack on container' unless retcode.zero?
  elsif destination_filetype_code == file_code && source_filename != destination_filename
    # If the destination was a file _and_ the filename is different than the source filename, then we need to rename it
    retcode = container.exec(['bash', '-c', "cd #{destination}; mv #{source_filename} #{destination_filename}"]).last
    raise "Unable to rename '#{source_filename}' to '#{destination_filename}' on container" unless retcode.zero?
  end
end

#force_remove_images(name_and_tag) ⇒ Object

Remove docker images with the “force” option set to true This will remove the images even if they are currently in use and cause unintended side effects.



160
161
162
163
# File 'lib/firespring_dev_commands/docker.rb', line 160

def force_remove_images(name_and_tag)
  images = ::Docker::Image.all(filter: name_and_tag)
  ::Docker::Image.remove(images[0].id, force: true) unless images.empty?
end

#mapped_public_port(name, private_port) ⇒ Object

Deprecated.

Calls the docker compose method with the given inputs



174
175
176
177
# File 'lib/firespring_dev_commands/docker.rb', line 174

def mapped_public_port(name, private_port)
  warn '[DEPRECATION] `Docker#mapped_public_port` is deprecated.  Please use `Docker::Compose#mapped_public_port` instead.'
  Docker::Compose.new.mapped_public_port(name, private_port)
end

Display a nicely formatted table of containers and their associated information



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/firespring_dev_commands/docker.rb', line 292

def print_containers
  idsize = 15
  imagesize = 20
  archsize = 7
  commandsize = 20
  createsize = 15
  statussize = 15
  portsize = 20
  namesize = 16
  total = idsize + imagesize + archsize + commandsize + createsize + statussize + portsize + namesize

  # If there is additional width available, add it to the repo and tag columns
  additional = [((Rake.application.terminal_width - total) / 3).floor, 0].max

  # If there's enough extra, give some to the name as well
  if additional > 40
    namesize += 15
    additional -= 5
  end
  imagesize += additional
  commandsize += additional
  portsize += additional

  format = "%-#{idsize}s%-#{imagesize}s%-#{archsize}s%-#{commandsize}s%-#{createsize}s%-#{statussize}s%-#{portsize}s%-#{namesize}s"
  puts format(format, :'CONTAINER ID', :IMAGE, :ARCH, :COMMAND, :CREATED, :STATUS, :PORTS, :NAMES)
  ::Docker::Container.all.each do |container|
    id, image, arch, command, created, status, ports, names = container_info(container)
    puts format(format, id, image.truncate(imagesize - 5), arch, command.truncate(commandsize - 5), created, status, ports.truncate(portsize - 5), names)
  end
end

rubocop:disable Metrics/ParameterLists Display a nicely formatted table of images and their associated information



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/firespring_dev_commands/docker.rb', line 245

def print_images
  reposize   = 70
  tagsize    = 70
  archsize   = 9
  imagesize  = 15
  createsize = 20
  sizesize   = 10
  total = reposize + tagsize + archsize + imagesize + createsize + sizesize

  # If there is additional width available, add it to the repo and tag columns
  additional = [((Rake.application.terminal_width - total) / 2).floor, 0].max
  reposize += additional
  tagsize += additional

  format = "%-#{reposize}s%-#{tagsize}s%-#{archsize}s%-#{imagesize}s%-#{createsize}s%s"
  puts format(format, :REPOSITORY, :TAG, :ARCH, :'IMAGE ID', :CREATED, :SIZE)
  ::Docker::Image.all.each do |image|
    image_info(image).each do |repo, tag, arch, id, created, size|
      puts format(format, repo, tag, arch, id, created, size)
    end
  end
end

#pruneObject

Prunes/removes all unused containers, networks, volumes, and images



55
56
57
58
59
60
# File 'lib/firespring_dev_commands/docker.rb', line 55

def prune
  prune_containers
  prune_networks
  prune_volumes
  prune_images
end

#prune_containersObject

Prunes/removes all unused containers



63
64
65
# File 'lib/firespring_dev_commands/docker.rb', line 63

def prune_containers
  _prune('containers')
end

#prune_imagesObject

Prunes/removes all unused images



81
82
83
# File 'lib/firespring_dev_commands/docker.rb', line 81

def prune_images
  _prune('images')
end

#prune_networksObject

Prunes/removes all unused networks



68
69
70
# File 'lib/firespring_dev_commands/docker.rb', line 68

def prune_networks
  _prune('networks')
end

#prune_volumesObject

Prunes/removes all unused volumes Specify ALL_VOLUMES=false in your environment to only clean anonymous volumes (docker version 23.x+)



74
75
76
77
78
# File 'lib/firespring_dev_commands/docker.rb', line 74

def prune_volumes
  opts = {}
  opts[:filters] = {all: ['true']}.to_json if Dev::Common.new.version_greater_than('22.9999.0', self.class.version) && ENV['ALL_VOLUMES'].to_s.strip != 'false'
  _prune('volumes', opts:)
end

#pull_image(name, tag = nil) ⇒ Object

Push the remote version of the docker image from the defined remote repository



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/firespring_dev_commands/docker.rb', line 135

def pull_image(name, tag = nil)
  unless tag
    if name.include?(':')
      name, tag = name.split(':')
    else
      tag = 'latest'
    end
  end

  puts "\nPulling #{name}:#{tag}"
  opts = {
    fromImage: "#{name}:#{tag}",
    platform: Dev::Platform.new.architecture
  }
  ::Docker::Image.create(**opts) { |response| Dev::Docker::Status.new.response_callback(response) }
end

#push_image(image, name, tag = nil) ⇒ Object

Push the local version of the docker image to the defined remote repository



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/firespring_dev_commands/docker.rb', line 121

def push_image(image, name, tag = nil)
  unless tag
    if name.include?(':')
      name, tag = name.split(':')
    else
      tag = 'latest'
    end
  end

  puts "Pushing to #{name}:#{tag}"
  image.push(::Docker.creds, repo_tag: "#{name}:#{tag}") { |response| Dev::Docker::Status.new.response_callback(response) }
end

#untag_image(image, name, tag) ⇒ Object

Remove the local version of the given docker image



153
154
155
156
# File 'lib/firespring_dev_commands/docker.rb', line 153

def untag_image(image, name, tag)
  puts "Untagging #{name}:#{tag}"
  image.remove(name: "#{name}:#{tag}")
end

#working_dir(container) ⇒ Object

Gets the default working dir of the container



180
181
182
# File 'lib/firespring_dev_commands/docker.rb', line 180

def working_dir(container)
  container.json['Config']['WorkingDir']
end