New Resource: Chocolatey Package (#2793)

* Adds chocolatey package resource
* Adds docs for chocolatey_package resource
* Differentiate chocolatey package from windows feature

Suggested by @frezbo

Signed-off-by: David Alexander <opensource@thelonelyghost.com>
This commit is contained in:
David Alexander 2018-04-05 08:54:27 -04:00 committed by Jared Quick
parent 3acbb47287
commit 3b97e16b97
6 changed files with 171 additions and 0 deletions

View file

@ -0,0 +1,58 @@
---
title: About the chocolatey_package Resource
platform: windows
---
# chocolatey_package
Use the `chocolatey_package` InSpec audit resource to test if the named [Chocolatey](https://chocolatey.org/) package and/or package version is installed on the system.
<br>
## Syntax
A `chocolatey_package` resource block declares the name of a Chocolatey package to be tested:
describe chocolatey_package('name') do
it { should be_installed }
end
where
* `('name')` must specify the (case-sensitive) name of a package, such as `'nssm'`
* `be_installed` is a valid matcher for this resource
<br>
## Examples
The following examples show how to use this InSpec audit resource
### Test if NSSM version 2.1.0 is installed
describe chocolatey_package('nssm') do
it { should be_installed }
its('version') { should eq '2.1.0' }
end
## Matchers
For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers).
### be_installed
The `be_installed` matcher tests if the named package is installed at all.
it { should be_installed }
### version
The `version` matcher tests if the named package version is on the system:
its('version') { should eq '2.1.0' }
You can also use the `cmp OPERATOR` matcher to perform comparisons using the version attribute:
its('version') { should cmp >= '1.93.4-13debug84' }
`cmp` understands version numbers using Gem::Version, and can use the operators `==, <, <=, >=, and >`. It will compare versions by each segment, not as a string - so '7.4' is smaller than '7.30', for example.

View file

@ -95,6 +95,7 @@ require 'resources/auditd_conf'
require 'resources/bash'
require 'resources/bond'
require 'resources/bridge'
require 'resources/chocolatey_package'
require 'resources/command'
require 'resources/cran'
require 'resources/cpan'

View file

@ -0,0 +1,78 @@
# encoding: utf-8
# frozen_string_literal: true
# Check for Chocolatey packages to be installed
module Inspec::Resources
class ChocoPkg < Inspec.resource(1)
name 'chocolatey_package'
supports platform: 'windows'
desc 'Use the chocolatey_package Inspec audit resource to test if the named package and/or package version is installed on the system.'
example <<-EOH
describe chocolatey_package('git') do
it { should be_installed }
its('version') { should eq '2.15.1' }
end
EOH
attr_reader :package_name
def initialize(package_name, _opts = {})
raise 'Chocolatey is not installed' unless inspec.command('choco').exist?
@package_name = package_name
@cache = base_data.update(generate_cache)
end
def installed?
@cache[:installed]
end
def info
@cache.dup
end
def respond_to_missing?(method_name, *)
@cache.key?(method_name) || super
end
def method_missing(method_name, *args, &block)
if @cache.key?(method_name)
@cache.fetch(method_name)
else
super
end
end
def to_s
"Chocolatey package #{package_name}"
end
private
def base_data
{
name: package_name,
version: nil,
installed: false,
type: 'chocolatey',
}
end
def generate_cache
command = <<-EOH
(choco list --local-only --exact --include-programs --limit-output '#{package_name.gsub("'", "\`'")}') -Replace "\\|", "=" | ConvertFrom-StringData | ConvertTo-JSON
EOH
cmd = inspec.powershell(command.strip)
return {} if cmd.exit_status != 0 || cmd.stdout.strip.empty?
out = JSON.parse(cmd.stdout)
return {
version: out.fetch(package_name),
installed: true,
}
rescue JSON::ParserError, KeyError
return {}
end
end
end

View file

@ -261,6 +261,10 @@ class MockLoader
'/test/path/pip show django' => cmd.call('pip-show-non-standard-django'),
"Get-Package -Name 'Mozilla Firefox' | ConvertTo-Json" => cmd.call('get-package-firefox'),
"Get-Package -Name 'Ruby 2.1.6-p336-x64' | ConvertTo-Json" => cmd.call('get-package-ruby'),
'Get-Command "choco"' => empty.call,
'bash -c \'type "choco"\'' => cmd_exit_1.call,
'(choco list --local-only --exact --include-programs --limit-output \'nssm\') -Replace "\|", "=" | ConvertFrom-StringData | ConvertTo-JSON' => cmd.call('choco-list-nssm'),
'(choco list --local-only --exact --include-programs --limit-output \'git\') -Replace "\|", "=" | ConvertFrom-StringData | ConvertTo-JSON' => empty.call,
"New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name 'dhcp'| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq 'dhcp' -or $_.DisplayName -eq 'dhcp'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json" => cmd.call('get-service-dhcp'),
"New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Pip -Value (Invoke-Command -ScriptBlock {where.exe pip}) -PassThru | Add-Member -MemberType NoteProperty -Name Python -Value (Invoke-Command -ScriptBlock {where.exe python}) -PassThru | ConvertTo-Json" => cmd.call('get-windows-pip-package'),
"Get-WindowsFeature | Where-Object {$_.Name -eq 'dhcp' -or $_.DisplayName -eq 'dhcp'} | Select-Object -Property Name,DisplayName,Description,Installed,InstallState | ConvertTo-Json" => cmd.call('get-windows-feature'),

View file

@ -0,0 +1,3 @@
{
"nssm": "2.24.101"
}

View file

@ -0,0 +1,27 @@
# encoding: utf-8
# author: David Alexander <opensource@thelonelyghost.com>
require 'helper'
require 'inspec/resource'
def skip(*args)
# noop
end
describe 'Inspec::Resources::ChocoPkg' do
it 'can parse output from `choco` when package is installed' do
pkg = { name: 'git', installed: false, version: nil, type: 'chocolatey' }
resource = MockLoader.new(:windows).load_resource('chocolatey_package', 'git')
_(resource.installed?).must_equal pkg[:installed]
_(resource.version).must_equal pkg[:version]
_(resource.info).must_equal pkg
end
it 'can parse output from `choco` when package not installed' do
pkg = { name: 'nssm', installed: true, version: '2.24.101', type: 'chocolatey' }
resource = MockLoader.new(:windows).load_resource('chocolatey_package', 'nssm')
_(resource.installed?).must_equal pkg[:installed]
_(resource.version).must_equal pkg[:version]
_(resource.info).must_equal pkg
end
end