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



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/firespring_dev_commands/docker.rb', line 217

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



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
209
210
211
212
213
# File 'lib/firespring_dev_commands/docker.rb', line 177

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.



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

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



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/firespring_dev_commands/docker.rb', line 284

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



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/firespring_dev_commands/docker.rb', line 237

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



98
99
100
# File 'lib/firespring_dev_commands/docker.rb', line 98

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



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

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



127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/firespring_dev_commands/docker.rb', line 127

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



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

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



172
173
174
# File 'lib/firespring_dev_commands/docker.rb', line 172

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