From 7bc38ccb58e6abf66d6141a3b2e9f60eb0077d71 Mon Sep 17 00:00:00 2001 From: Jesse Hathaway Date: Thu, 11 May 2023 14:19:43 -0500 Subject: [PATCH] (FACT-3202) Add is_virtual and virtual support for crio 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 explicit support for podman and returns container_other when the container runtime is not explicitly supported. --- lib/facter/framework/core/file_loader.rb | 1 + lib/facter/resolvers/containers.rb | 56 +++++++++++++++-------- lib/facter/util/linux/proc.rb | 21 +++++++++ spec/facter/resolvers/containers_spec.rb | 25 +++++++--- spec/facter/util/linux/proc_spec.rb | 38 +++++++++++++++ spec/fixtures/proc_environ_no_value | Bin 0 -> 28 bytes spec/fixtures/proc_environ_podman | Bin 0 -> 127 bytes 7 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 lib/facter/util/linux/proc.rb create mode 100644 spec/facter/util/linux/proc_spec.rb create mode 100644 spec/fixtures/proc_environ_no_value create mode 100644 spec/fixtures/proc_environ_podman diff --git a/lib/facter/framework/core/file_loader.rb b/lib/facter/framework/core/file_loader.rb index 22e3d17a88..cb1b2f5034 100644 --- a/lib/facter/framework/core/file_loader.rb +++ b/lib/facter/framework/core/file_loader.rb @@ -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' diff --git a/lib/facter/resolvers/containers.rb b/lib/facter/resolvers/containers.rb index 2bae2f35a7..52d3c00f48 100644 --- a/lib/facter/resolvers/containers.rb +++ b/lib/facter/resolvers/containers.rb @@ -14,45 +14,65 @@ 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 read_environ(fact_name) + begin + container = Facter::Util::Linux::Proc.getenv_for_pid(1, 'container') + rescue StandardError => e + log.warn("Unable to getenv for pid 1, '#{e}'") + return nil + end + return if container.nil? || container.empty? + + 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' + log.warn("Container runtime, '#{container}', is unsupported, setting to, '#{vm}'") + 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, lxc_from_environ) + 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 diff --git a/lib/facter/util/linux/proc.rb b/lib/facter/util/linux/proc.rb new file mode 100644 index 0000000000..a1a85d525f --- /dev/null +++ b/lib/facter/util/linux/proc.rb @@ -0,0 +1,21 @@ +# 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: true) + lines.each do |line| + key, value = line.split('=', 2) + return value if key == field + end + nil + end + end + end + end + end +end diff --git a/spec/facter/resolvers/containers_spec.rb b/spec/facter/resolvers/containers_spec.rb index fdb76c1dec..1941469e79 100644 --- a/spec/facter/resolvers/containers_spec.rb +++ b/spec/facter/resolvers/containers_spec.rb @@ -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', [], "\0", chomp: true) .and_return(environ_output) end @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -91,4 +91,15 @@ expect(containers_resolver.resolve(:hypervisor)).to eq(result) end end + + context 'when hypervisor is an unknown container runtime discovered by environ' do + let(:cgroup_output) { load_fixture('cgroup_file').read } + let(:environ_output) { ['container=UNKNOWN'] } + let(:logger) { Facter::Log.class_variable_get(:@@logger) } + + it 'return container_other for vm' do + expect(logger).to receive(:warn).with(/Container runtime, 'UNKNOWN', is unsupported, setting to, 'container_other'/) + expect(containers_resolver.resolve(:vm)).to eq('container_other') + end + end end diff --git a/spec/facter/util/linux/proc_spec.rb b/spec/facter/util/linux/proc_spec.rb new file mode 100644 index 0000000000..ff81235f27 --- /dev/null +++ b/spec/facter/util/linux/proc_spec.rb @@ -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', [], "\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 diff --git a/spec/fixtures/proc_environ_no_value b/spec/fixtures/proc_environ_no_value new file mode 100644 index 0000000000000000000000000000000000000000..e39a3e94e4d9e65968a2493a897541d4515a0cd3 GIT binary patch literal 28 jcmYc)O-jm1Ew*I{aSif~4+!#i4|4RiEy*pdVBi7(h^h$T literal 0 HcmV?d00001 diff --git a/spec/fixtures/proc_environ_podman b/spec/fixtures/proc_environ_podman new file mode 100644 index 0000000000000000000000000000000000000000..41c5dc9895d358a4fbf25a5bb68d294a64498b05 GIT binary patch literal 127 zcmWG>4f3_EC`m2KWk}A?D@n}EOD(c3$WO^l%wq^}4DqnlFD)+8&&f|t%+W7S%FMGu u;={QJ39t}^3uH2Q`1`ur>KEnbmjIc;A%2cPu9=CMVQP|vVQQkWAp-y%rYbT3 literal 0 HcmV?d00001