Skip to content

Commit

Permalink
add "packages" resource
Browse files Browse the repository at this point in the history
This pull request adds a packages resource so that we can check for pattern matches against all the packages on a system. This initially implements only dpkg support for debian-based platforms so we can cover this use case:

```ruby
describe packages(/^xserver-xorg.*/) do
  its("list") { should be_empty }
end
```

This uses FilterTable so we can supply additional queries, too.

```ruby
describe packages(/vi.+/).where { status != 'installed' } do
  its('statuses') { should be_empty }
end
```

Users can specify the name as a string or a regular expression. If it is a string, we will escape it and convert it to a regular expression to use in matching against the full returned list of packages. If it is a regular expression, we take that as is and use it to filter the results.

While some package management systems such as `dpkg` can take a shell glob argument to filter their results, we eschew this and require a regular expression to match multiple package names because we will need this to work across other platforms in the future. This means that the following:

```ruby
packages("vim")
```

Will return *all* the "vim" packages on the system. The `packages` resource will take `"vim"`, turn it into `/vim/`, and greedily match anything with "vim" in the name. To match only a single package named `vim`, it needs to be an anchored regular expression.

```ruby
packages(/^vim$/)
```

Signed-off-by: Joshua Timberman <[email protected]>

Use entries instead of list

Added a few more tests and non installed package in output
Signed-off-by: Alex Pop <[email protected]>

fix lint

Signed-off-by: Alex Pop <[email protected]>

Signed-off-by: Joshua Timberman <[email protected]>
  • Loading branch information
jtimberman authored and alexpop committed Feb 7, 2017
1 parent f8d319c commit d7fad68
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/inspec/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def self.validate_resource_dsl_version!(version)
require 'resources/os'
require 'resources/os_env'
require 'resources/package'
require 'resources/packages'
require 'resources/parse_config'
require 'resources/passwd'
require 'resources/pip'
Expand Down
86 changes: 86 additions & 0 deletions lib/resources/packages.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# encoding: utf-8
# copyright: 2017, Chef Software, Inc. <[email protected]>
# author: Joshua Timberman
# author: Alex Pop
# license: All rights reserved

require 'utils/filter'

module Inspec::Resources
class Packages < Inspec.resource(1)
name 'packages'
desc 'Use the packages InSpec audit resource to test properties for multiple packages installed on the system'
example "
describe packages(/xserver-xorg.*/) do
its('entries') { should be_empty }
end
describe packages('vim').entries.length do
it { should be > 1 }
end
describe packages(/vi.+/).where { status != 'installed' } do
its('statuses') { should be_empty }
end
"

def initialize(pattern)
@pattern = pattern_regexp(pattern)
all_pkgs = package_list
@list = all_pkgs.find_all do |hm|
hm[:name] =~ pattern_regexp(pattern)
end
end

def to_s
"Packages #{@pattern.class == String ? @pattern : @pattern.inspect}"
end

filter = FilterTable.create
filter.add_accessor(:where)
.add_accessor(:entries)
.add(:statuses, field: 'status', style: :simple)
.add(:names, field: 'name')
.add(:versions, field: 'version')
.connect(self, :filtered_packages)

private

def pattern_regexp(p)
if p.class == String
Regexp.new(Regexp.escape(p))
elsif p.class == Regexp
p
else
fail 'invalid name argument to packages resource, please use a "string" or /regexp/'
end
end

def filtered_packages
@list
end

def package_list
os = inspec.os

if os.debian?
command = "dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\\n'"
else
fail "packages resource is not yet supported on #{os.name}"
end
build_package_list(command)
end

Package = Struct.new(:status, :name, :version)

def build_package_list(command)
cmd = inspec.command(command)
all = cmd.stdout.split("\n")[1..-1]
return [] if all.nil?
all.map do |m|
a = m.split
a[0] = 'installed' if a[0] =~ /^.i/
a[2] = a[2].split(':').last
Package.new(*a)
end
end
end
end
3 changes: 3 additions & 0 deletions test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class MockLoader
ubuntu1204: { name: 'ubuntu', family: 'debian', release: '12.04', arch: 'x86_64' },
ubuntu1404: { name: 'ubuntu', family: 'debian', release: '14.04', arch: 'x86_64' },
ubuntu1504: { name: 'ubuntu', family: 'debian', release: '15.04', arch: 'x86_64' },
ubuntu1604: { name: 'ubuntu', family: 'debian', release: '16.04', arch: 'x86_64' },
mint17: { name: 'linuxmint', family: 'debian', release: '17.3', arch: 'x86_64' },
mint18: { name: 'linuxmint', family: 'debian', release: '18', arch: 'x86_64' },
windows: { name: 'windows', family: 'windows', release: '6.2.9200', arch: 'x86_64' },
Expand Down Expand Up @@ -249,6 +250,8 @@ def md.directory?
'pkginfo -l SUNWzfsr' => cmd.call('pkginfo-l-SUNWzfsr'),
# solaris 11 package manager
'pkg info system/file-system/zfs' => cmd.call('pkg-info-system-file-system-zfs'),
# dpkg-query package list
"dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\\n'" => cmd.call('dpkg-query-W'),
# port netstat on solaris 10 & 11
'netstat -an -f inet -f inet6' => cmd.call('s11-netstat-an-finet-finet6'),
# xinetd configuration
Expand Down
12 changes: 12 additions & 0 deletions test/unit/mock/cmd/dpkg-query-W
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ii bash 4.3-14ubuntu1.1
rc fakeroot 1.20.2-1ubuntu1
rc libfakeroot 1.20.2-1ubuntu1
ii overlayroot 0.27ubuntu1.2
ii vim 2:7.4.1689-3ubuntu1.2
ii vim-common 2:7.4.1689-3ubuntu1.2
ii xorg 1:7.7+13ubuntu3
ii xorg-docs-core 1:1.7.1-1ubuntu1
ii xserver-common 2:1.18.4-0ubuntu0.2
ii xserver-xorg 1:7.7+13ubuntu3
ii xserver-xorg-core 2:1.18.4-0ubuntu0.2
ii xserver-xorg-input-all 1:7.7+13ubuntu3
59 changes: 59 additions & 0 deletions test/unit/resources/packages_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# encoding: utf-8
# author: Joshua Timberman

require 'helper'
require 'inspec/resource'

describe 'Inspec::Resources::Packages' do
it 'verify packages resource' do
resource = MockLoader.new(:ubuntu1604).load_resource('packages', /^vim$/)
_(resource.entries.length).must_equal 1
_(resource.entries[0].to_h).must_equal({
status: 'installed',
name: 'vim',
version: '7.4.1689-3ubuntu1.2',
})
end

it 'package name matches with output (string)' do
resource = MockLoader.new(:ubuntu1604).load_resource('packages', 'xserver-xorg')
_(resource.to_s).must_equal 'Packages /xserver\\-xorg/'
end

it 'packages using where filters' do
resource = MockLoader.new(:ubuntu1604).load_resource('packages', /.+root$/)
_(resource.entries.length).must_equal 3
_(resource.where { status != 'installed' }.names).must_equal(['fakeroot', 'libfakeroot'])
_(resource.where { version =~ /^0\.2.+/ }.entries[0].to_h).must_equal({
status: "installed",
name: "overlayroot",
version: "0.27ubuntu1.2",
})
end

it 'package name matches with output (regex)' do
resource = MockLoader.new(:ubuntu1604).load_resource('packages', /vim/)
_(resource.to_s).must_equal 'Packages /vim/'
end

it 'returns a list of packages with a wildcard' do
resource = MockLoader.new(:ubuntu1604).load_resource('packages', /^xserver-xorg.*/)
_(resource.statuses).must_equal ['installed']
_(resource.entries.length).must_equal 3
end


it 'fails on non debian platforms' do
proc {
resource = MockLoader.new(:centos6).load_resource('packages', 'bash')
resource.send(:entries, nil)
}.must_raise(RuntimeError)
end

it 'fails if the packages name is not a string or regexp' do
proc {
resources = MockLoader.new(:ubuntu1604).load_resource('packages', [:a, :b])
resources.send(:entries, nil)
}.must_raise(RuntimeError)
end
end

0 comments on commit d7fad68

Please sign in to comment.