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,
lib/firespring_dev_commands/docker/artifact.rb

Overview

Class contains many useful methods for interfacing with the docker api

Defined Under Namespace

Classes: Artifact, 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

#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



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/firespring_dev_commands/docker.rb', line 233

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 "#{source_path} 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



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
222
223
224
225
226
227
228
229
# File 'lib/firespring_dev_commands/docker.rb', line 193

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.



182
183
184
185
# File 'lib/firespring_dev_commands/docker.rb', line 182

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

Display a nicely formatted table of containers and their associated information



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/firespring_dev_commands/docker.rb', line 300

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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/firespring_dev_commands/docker.rb', line 253

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



114
115
116
# File 'lib/firespring_dev_commands/docker.rb', line 114

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_project_volumes(project_name:) ⇒ Object

Prunes all volumes which start wth the given project name



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/firespring_dev_commands/docker.rb', line 81

def prune_project_volumes(project_name:)
  project_name = project_name.to_s.strip
  raise 'No project name defined' if project_name.empty?

  ::Docker::Volume.all.each do |volume|
    next unless volume.info['Name'].start_with?(project_name)

    begin
      volume.remove
      LOG.info "Removed volume #{volume.id[0, 12]}"
    rescue => e
      LOG.error "Error removing volume #{volume.id[0, 12]}: #{e}"
    end
  end
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



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/firespring_dev_commands/docker.rb', line 157

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



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/firespring_dev_commands/docker.rb', line 143

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

#stop_project_containers(project_name:) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/firespring_dev_commands/docker.rb', line 97

def stop_project_containers(project_name:)
  project_name = project_name.to_s.strip
  raise 'No project name defined' if project_name.empty?

  ::Docker::Container.all(filters: {status: %w(restarting running)}.to_json).each do |container|
    next unless container.info&.dig('Names')&.map { |name| name.split('/').last }&.any? { |it| it.start_with?(project_name) }

    begin
      container.stop
      LOG.info "Stopped container #{container.id[0, 12]}"
    rescue => e
      LOG.error "Error stopping container #{contianer.id[0, 12]}: #{e}"
    end
  end
end

#untag_image(image, name, tag) ⇒ Object

Remove the local version of the given docker image



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

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



188
189
190
# File 'lib/firespring_dev_commands/docker.rb', line 188

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