1

I'm attempting to create an inspec control that searches through every line that starts with kernel (and ignores whitespace) in /boot/grub/grub.conf and then checking every line to see if it has 'nousb' somewhere in the line. I'd like it to return a failure if nousb is not found.

My issue is that I cannot figure out a way to grab/describe multiple stdouts from the grep in the event more than one line starts with kernel. Is that something I can accomplish using .each? Here is my code:

control 'os-disable-usb-pcmcia' do
  impact 0.5
  title 'Disable USB and PCMCIA Devices'
  desc "ensure default kernel in grub has 'nousb' option"

  output = command('egrep "^[[:space:]]*kernel" /boot/grub/grub.conf')
  describe output do
    its('stdout') { should match /^\s*kernel[^#]*nousb\b/ } # match nousb anywhere in the 'kernel' line
  end
end

Edit for clarification.

Say I have these lines in my conf file

kernel 1 nousb
kernel 2
kernel 3
kernel 4

the test will consider it passing because the initial one matched what it was looking for despite multiple kernel lines not having the nousb requirement.

StephenKing
  • 36,187
  • 11
  • 83
  • 112
Blooze
  • 1,987
  • 4
  • 16
  • 19
  • I am not sure about `chef` but 1. why would not you `egrep` for `^\s*kernel[^#]*nousb\b` and check the result for truthy? and 2. are you sure `command` returns the `stdout`, not the result of command execution (I believe it’s `0` here?) – Aleksei Matiushkin Sep 20 '16 at 19:00
  • That's a good call I will switch my egrep to exactly what I am matching for, big oversight on my part. And I think it is actually checking the stdout, since when I run the test, it passes that the output matches the pattern I'm looking for. – Blooze Sep 20 '16 at 19:09
  • If it returns the `stdout`, you should be fine with what you were doing, since `match` works perfectly on multiline and `egrep` returns the plain string. There are no “multiple stdouts“ in the universe :) – Aleksei Matiushkin Sep 20 '16 at 19:14
  • The issue though is the inspec portion, the describe command that I am running on the stdout is only checking one of the lines and considering it passing, I will edit my comment and clarify. – Blooze Sep 20 '16 at 19:17

3 Answers3

1

Use a file resource and Ruby's RegExp matching (as you are already doing).

describe file('/boot/grub/grub.conf') do
  its(:content) { is_expected.to match /whatever/ }
end

EDIT to show how to do this with Ruby code instead of fancy regexps:

describe file('/boot/grub/grub.conf') do
  it do
    # Get all the lines in the file.
    lines = subject.content.split(/\n/)
    # Check for any line that _doesn't_ match the regexp.
    without_nousb = lines.any? {|line| line !~ /^.*?kernel.*?nousb.*?$/ }
    # Make a test assertion.
    expect(without_nousb).to be false
  end
end
coderanger
  • 52,400
  • 4
  • 52
  • 75
  • Thanks for reaching out, I am however having the same issue where that describe will find one line that fits its match and mark it as passing, when I have lines that should be failing. (a line with kernel that does not have nousb on it) – Blooze Sep 20 '16 at 19:25
  • Then your regexp is bad, please include it above. You probably want something like this: `is_expected.to_not match /^\s*kernel \d+(?! nousb)$/m` – coderanger Sep 20 '16 at 19:31
  • He's trying to ensure that *each* kernel line has `nousb`. The :content matcher will not suffice by itself. – Todd A. Jacobs Sep 20 '16 at 20:28
  • Hence the `to_not` reverse matcher and a negative lookahead :) But it's definitely easier to write this in Ruby code than in a super-complex regexp. – coderanger Sep 20 '16 at 20:30
  • I'm a bit confused on the to_not part since the above mentions that it is expected to match, but do you have any references where I could read on doing this with Ruby instead of regex? I'm pretty new to programming overall. Thanks – Blooze Sep 20 '16 at 20:52
  • @coderanger `its(:content) { is_expected.to_not match /{#regex}/m }` looks like it would work, but it doesn't. Besides being hard to read because of the inversion, what would you expect `"kernel foo\nbar" !~ /^\s*kernel.*?(?!nousb)/m` to return? – Todd A. Jacobs Sep 20 '16 at 21:06
  • Compiling zero width assertions in my brain is beyond my skills usually, I just load up regexpal.com and try it out until I'm happy. – coderanger Sep 20 '16 at 21:18
  • I added a probably clearer version that uses a bit more code. – coderanger Sep 20 '16 at 21:23
0

Test Your Parse Results, Not a Content Match

You have to write some code inside the block that will parse your grub.conf file and ensure that there is a nousb command for each record. You might also try the Augeas lens for grub to validate the grub.conf for you. For example:

describe 'validate kernel parameters' do
  it 'should include a "nousb" parameter on each line' do
    # 1. parse your file however you like, then
    # 2. store result as a Boolean.
    expect(result).to be_true
  end
end

Check Running Kernel

Alternatively, if you only care about the result of the running kernel, you can check the value with something similar to:

# 1. read the remote's /proc/cmdline however you like, then
# 2. evaluate your match.
expect(command_line_contents).to match /\bnousb\b/
Community
  • 1
  • 1
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • This is not related to InSpec (or Serverspec's remote mode) as it does not run on the target. – coderanger Sep 20 '16 at 20:13
  • @coderanger This is covered by "parse the file however you like." I'm not prescribing the method for parsing the remote file; I'm explaining how to create an effective spec for the validation. – Todd A. Jacobs Sep 20 '16 at 20:20
  • With the edit, this is now better :) Before you had a `File.read` which will have no effect on a remote system. – coderanger Sep 20 '16 at 20:31
-1

I finally got the question. Since you want to check all kernels:

output = command('egrep "^[[:space:]]*kernel" /boot/grub/grub.conf')
ok = output.split("\n")
           .group_by { |line| line[/(?<=kernel )(\S*)/] }
           .map { |kernel, lines| lines.any? { |line| line =~ /nousb/ } }
           .all?

describe output do
  # OK must be TRUE
end

Here we:

  • split the output by lines
  • group lines by kernel name (regexp might require adjustment)
  • map the hash { kernel ⇒ lines } to true/false, depending on whether nousb is presented
  • check that all kernels have this nousb line.

The code above is aware of grub.cfg might contain many lines for the same kernel. Hope it helps.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160