Skip to content

Commit

Permalink
(FACT-3202) Add is_virtual and virtual support for crio
Browse files Browse the repository at this point in the history
Prior to this change facter returned:

    $ facter is_virtual
    false
    $ facter virtual
    physical

After this change facter returns:

    $ facter is_virtual
    true
    $ facter virtual
    crio

This change separates out reading pid 1's environment from proc and
reading the cgroup information. It also adds a container_other type when
the container runtime is not explicitly supported.
  • Loading branch information
lollipopman committed May 22, 2024
1 parent d04a42d commit 21afec5
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 29 deletions.
1 change: 1 addition & 0 deletions lib/facter/framework/core/file_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@
when 'linux'
require_relative '../../util/linux/dhcp'
require_relative '../../util/linux/if_inet6'
require_relative '../../util/linux/proc'
require_relative '../../util/linux/routing_table'
require_relative '../../util/linux/socket_parser'

Expand Down
50 changes: 32 additions & 18 deletions lib/facter/resolvers/containers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,59 @@ class << self
private

def post_resolve(fact_name, _options)
@fact_list.fetch(fact_name) { read_cgroup(fact_name) }
@fact_list.fetch(fact_name) do
read_environ(fact_name) || read_cgroup(fact_name)
end
end

def read_cgroup(fact_name)
output_cgroup = Facter::Util::FileHelper.safe_read('/proc/1/cgroup', nil)
output_environ = Facter::Util::FileHelper.safe_read('/proc/1/environ', nil)
return unless output_cgroup && output_environ
return unless output_cgroup

output_docker = %r{docker/(.+)}.match(output_cgroup)
output_lxc = %r{^/lxc/([^/]+)}.match(output_cgroup)
lxc_from_environ = /container=lxc/ =~ output_environ

info, vm = extract_vm_and_info(output_docker, output_lxc, lxc_from_environ)
info, vm = extract_for_nspawn(output_environ) unless vm
info, vm = extract_vm_and_info(output_docker, output_lxc)
@fact_list[:vm] = vm
@fact_list[:hypervisor] = { vm.to_sym => info } if vm
@fact_list[fact_name]
end

def extract_vm_and_info(output_docker, output_lxc, lxc_from_environ)
def read_environ(fact_name)
container = Facter::Util::Linux::Proc.getenv_for_pid(1, 'container')
return unless container && container != ''

info = {}
case container
when 'lxcroot'
vm = 'lxc'
when 'podman'
vm = 'podman'
when 'crio'
vm = 'crio'
when 'systemd-nspawn'
vm = 'systemd_nspawn'
info = { 'id' => Facter::Util::FileHelper.safe_read('/etc/machine-id', nil).strip }
else
vm = 'container_other'
end
@fact_list[:vm] = vm
@fact_list[:hypervisor] = { vm.to_sym => info } if vm
@fact_list[fact_name]
end

def extract_vm_and_info(output_docker, output_lxc)
vm = nil
if output_docker
vm = 'docker'
info = output_docker[1]
elsif output_lxc
vm = 'lxc'
info = output_lxc[1]
end
vm = 'lxc' if output_lxc || lxc_from_environ
info = output_lxc[1] if output_lxc

[info ? { INFO[vm] => info } : {}, vm]
end

def extract_for_nspawn(output_environ)
nspawn = /container=systemd-nspawn/ =~ output_environ
if nspawn
vm = 'systemd_nspawn'
info = Facter::Util::FileHelper.safe_read('/etc/machine-id', nil)
end
[info ? { 'id' => info.strip } : {}, vm]
end
end
end
end
Expand Down
6 changes: 4 additions & 2 deletions lib/facter/util/file_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ def safe_read(path, default_return = '')
default_return
end

def safe_readlines(path, default_return = [])
return File.readlines(path, encoding: Encoding::UTF_8) if File.readable?(path)
# rubocop:disable Style/SpecialGlobalVars
def safe_readlines(path, default_return = [], sep = $/, chomp: false)
return File.readlines(path, sep, chomp: chomp, encoding: Encoding::UTF_8) if File.readable?(path)

log_failed_to_read(path)
default_return
end
# rubocop:enable Style/SpecialGlobalVars

# This previously acted as a helper method for versions of Ruby older
# than 2.5, before Dir.children was added. As it isn't a private
Expand Down
22 changes: 22 additions & 0 deletions lib/facter/util/linux/proc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Facter
module Util
module Linux
class Proc
class << self
def getenv_for_pid(pid, field)
path = "/proc/#{pid}/environ"
lines = Facter::Util::FileHelper.safe_readlines(path, [], "\0", chomp: false)
lines.each do |line|
if line.slice(0, field.length) == field && line.slice(field.length) == '='
return line.slice(field.length + 1, line.length - (field.length + 1))
end
end
nil
end
end
end
end
end
end
14 changes: 7 additions & 7 deletions spec/facter/resolvers/containers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/proc/1/cgroup', nil)
.and_return(cgroup_output)
allow(Facter::Util::FileHelper).to receive(:safe_read)
.with('/proc/1/environ', nil)
allow(Facter::Util::FileHelper).to receive(:safe_readlines)
.with('/proc/1/environ', nil, "\0", chomp: true)
.and_return(environ_output)
end

Expand All @@ -18,7 +18,7 @@

context 'when hypervisor is docker' do
let(:cgroup_output) { load_fixture('docker_cgroup').read }
let(:environ_output) { 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' }
let(:environ_output) { ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'] }
let(:result) { { docker: { 'id' => 'ee6e3c05422f1273c9b41a26f2b4ec64bdb4480d63a1ad9741e05cafc1651b90' } } }

it 'return docker for vm' do
Expand All @@ -32,7 +32,7 @@

context 'when hypervisor is nspawn' do
let(:cgroup_output) { load_fixture('cgroup_file').read }
let(:environ_output) { 'PATH=/usr/local/sbin:/bincontainer=systemd-nspawnTERM=xterm-256color' }
let(:environ_output) { ['PATH=/usr/local/sbin:/bin', 'container=systemd-nspawn', 'TERM=xterm-256color'] }
let(:result) { { systemd_nspawn: { 'id' => 'ee6e3c05422f1273c9b41a26f2b4ec64bdb4480d63a1ad9741e05cafc1651b90' } } }

before do
Expand All @@ -52,7 +52,7 @@

context 'when hypervisor is lxc and it is discovered by cgroup' do
let(:cgroup_output) { load_fixture('lxc_cgroup').read }
let(:environ_output) { 'PATH=/usr/local/sbin:/sbin:/bin' }
let(:environ_output) { ['PATH=/usr/local/sbin:/sbin:/bin'] }
let(:result) { { lxc: { 'name' => 'lxc_container' } } }

it 'return lxc for vm' do
Expand All @@ -66,7 +66,7 @@

context 'when hypervisor is lxc and it is discovered by environ' do
let(:cgroup_output) { load_fixture('cgroup_file').read }
let(:environ_output) { 'container=lxcroot' }
let(:environ_output) { ['container=lxcroot'] }
let(:result) { { lxc: {} } }

it 'return lxc for vm' do
Expand All @@ -80,7 +80,7 @@

context 'when hypervisor is neighter lxc nor docker' do
let(:cgroup_output) { load_fixture('cgroup_file').read }
let(:environ_output) { 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin' }
let(:environ_output) { ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin'] }
let(:result) { nil }

it 'return lxc for vm' do
Expand Down
7 changes: 5 additions & 2 deletions spec/facter/util/file_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
let(:path) { '/Users/admin/file.txt' }
let(:entries) { ['file.txt', 'a'] }
let(:content) { 'file content' }
# rubocop:disable Style/SpecialGlobalVars
let(:sep) { $/ }
# rubocop:enable Style/SpecialGlobalVars
let(:error_message) do
"Facter::Util::FileHelper - #{Facter::CYAN}File at: /Users/admin/file.txt is not accessible.#{Facter::RESET}"
end
Expand Down Expand Up @@ -115,7 +118,7 @@

describe '#safe_read_lines' do
before do
allow(File).to receive(:readlines).with(path, anything).and_return(array_content)
allow(File).to receive(:readlines).with(path, sep, anything).and_return(array_content)
end

context 'when successfully read the file lines' do
Expand All @@ -134,7 +137,7 @@
it 'File.readlines is called with the correct path' do
file_helper.safe_readlines(path)

expect(File).to have_received(:readlines).with(path, anything)
expect(File).to have_received(:readlines).with(path, sep, anything)
end

it "doesn't log anything" do
Expand Down
38 changes: 38 additions & 0 deletions spec/facter/util/linux/proc_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

describe Facter::Util::Linux::Proc do
describe '#getenv_for_pid' do
before do
allow(Facter::Util::FileHelper).to receive(:safe_readlines)
.with('/proc/1/environ', nil, "\0", { chomp: true })
.and_return(proc_environ.readlines("\0", chomp: true))
end

context 'when field exists' do
let(:proc_environ) { load_fixture('proc_environ_podman') }

it 'returns the field' do
result = Facter::Util::Linux::Proc.getenv_for_pid(1, 'container')
expect(result).to eq('podman')
end
end

context 'when field does not exist' do
let(:proc_environ) { load_fixture('proc_environ_podman') }

it 'returns nil' do
result = Facter::Util::Linux::Proc.getenv_for_pid(1, 'butter')
expect(result).to eq(nil)
end
end

context 'when field is empty' do
let(:proc_environ) { load_fixture('proc_environ_no_value') }

it 'returns an empty string' do
result = Facter::Util::Linux::Proc.getenv_for_pid(1, 'bubbles')
expect(result).to eq('')
end
end
end
end
Binary file added spec/fixtures/proc_environ_no_value
Binary file not shown.
Binary file added spec/fixtures/proc_environ_podman
Binary file not shown.

0 comments on commit 21afec5

Please sign in to comment.