Class: Bootprint::Docker::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/bootprint/docker.rb

Constant Summary collapse

CAPTURE_SCRIPT =
<<~'RUBY'
  require "json"
  require "rbconfig"
  require "rubygems"
  require "openssl"
  require "psych"
  packages = []
  if File.file?("/var/lib/dpkg/status")
    packages = File.read("/var/lib/dpkg/status").scan(/^Package:\s*(\S+)/).flatten
  elsif File.file?("/lib/apk/db/installed")
    packages = File.read("/lib/apk/db/installed").scan(/^P:(\S+)/).flatten
  end
  gems = Gem::Specification.map do |spec|
    [spec.name, {
      "version" => spec.version.to_s,
      "platform" => spec.platform.to_s,
      "native_extensions" => !spec.extensions.empty?,
      "missing_extensions" => spec.respond_to?(:missing_extensions?) && spec.missing_extensions?,
      "prerelease" => spec.version.prerelease?
    }]
  end.sort.to_h
  output = {
    "runtime" => {
      "engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby",
      "engine_version" => defined?(RUBY_ENGINE_VERSION) ? RUBY_ENGINE_VERSION : RUBY_VERSION,
      "ruby_version" => RUBY_VERSION,
      "patchlevel" => RUBY_PATCHLEVEL,
      "platform" => RUBY_PLATFORM,
      "architecture" => RbConfig::CONFIG["arch"],
      "host_os" => RbConfig::CONFIG["host_os"],
      "host_cpu" => RbConfig::CONFIG["host_cpu"],
      "description" => RUBY_DESCRIPTION,
      "debug_build" => RbConfig::CONFIG["configure_args"].to_s.include?("--enable-debug"),
      "supported" => Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1.0")
    },
    "dependencies" => {
      "toolchain" => {
        "rubygems_version" => Gem::VERSION,
        "bundler_version" => Gem.loaded_specs["bundler"]&.version&.to_s
      },
      "gems" => gems,
      "lockfile" => { "platforms" => [] }
    },
    "native_libraries" => {
      "openssl" => {
        "compiled" => OpenSSL::OPENSSL_VERSION,
        "runtime" => OpenSSL.const_defined?(:OPENSSL_LIBRARY_VERSION) ? OpenSSL::OPENSSL_LIBRARY_VERSION : OpenSSL::OPENSSL_VERSION
      },
      "libyaml" => Psych.libyaml_version.join("."),
      "psych" => Psych::VERSION
    },
    "configuration" => {
      "environment_variables" => ENV.keys.sort.to_h { |name| [name, true] },
      "required_environment_variables" => [],
      "optional_environment_variables" => [],
      "rails" => {}
    },
    "filesystem" => {
      "path_separator" => File::SEPARATOR,
      "case_sensitive" => true,
      "symlinks_supported" => File.respond_to?(:symlink),
      "temporary_directory" => { "present" => File.directory?("/tmp"), "writable" => File.writable?("/tmp"), "path" => "/tmp" },
      "required_directories" => { "current" => { "present" => File.directory?(Dir.pwd), "writable" => File.writable?(Dir.pwd), "path" => Dir.pwd } }
    },
    "operating_system" => {
      "name" => RbConfig::CONFIG["host_os"],
      "cpu" => RbConfig::CONFIG["host_cpu"],
      "capabilities" => {
        "make" => ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, "make")) },
        "gcc" => ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, "gcc")) }
      },
      "ruby_headers" => { "present" => File.directory?(RbConfig::CONFIG["rubyhdrdir"].to_s) },
      "system_packages" => packages.sort
    }
  }
  STDOUT.write(JSON.generate(output))
RUBY

Instance Method Summary collapse

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


88
89
90
91
92
93
# File 'lib/bootprint/docker.rb', line 88

def available?
  _out, _error, status = Open3.capture3("docker", "version", "--format", "{{.Client.Version}}")
  status.success?
rescue Errno::ENOENT
  false
end

#capture(image, label: nil, privacy: :standard) ⇒ Object



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
# File 'lib/bootprint/docker.rb', line 95

def capture(image, label: nil, privacy: :standard)
  raise DockerError, "Docker is unavailable; install Docker and ensure its daemon is running" unless available?

   = (image)
  container_name = "bootprint-#{SecureRandom.hex(8)}"
  stdout, stderr, status = Open3.capture3(
    "docker", "run", "--name", container_name, "--rm", "--network", "none", "--read-only", "--entrypoint", "ruby",
    image.to_s, "-e", CAPTURE_SCRIPT
  )
  raise DockerError, "Could not inspect image #{image}: #{Sanitizer.text(stderr.strip)}" unless status.success?

  environment = JSON.parse(stdout)
  environment["name"] = label || image.to_s
  environment["container"] = 
  Snapshot.new(
    "schema_version" => Schema::CURRENT_VERSION,
    "generated_at" => Time.now.utc.iso8601,
    "bootprint_version" => VERSION,
    "environment" => Sanitizer.recursive(environment, privacy:),
    "capture" => { "privacy" => privacy.to_s, "source" => "docker", "network" => "disabled" }
  )
rescue JSON::ParserError => error
  raise DockerError, "Image #{image} returned invalid inspection data: #{error.message}"
ensure
  cleanup_container(container_name) if container_name
end