diff --git a/lib/matchers/matchers.rb b/lib/matchers/matchers.rb index 5c62b6218..29b9e920c 100644 --- a/lib/matchers/matchers.rb +++ b/lib/matchers/matchers.rb @@ -88,3 +88,23 @@ RSpec::Matchers.define :contain_duplicates do !dup.uniq.empty? end end + +# for packages +RSpec::Matchers.define :be_installed do + match do |package| + package.installed? == true + end + + failure_message do |package| + "expected that `#{package}` is installed" + end + + chain :by do + fail "[UNSUPPORTED] Please use the new resources 'gem', 'npm' or 'pip'." + end + + chain :with_version do |version| + warn "[DEPRECATION] `with_version` is deprecated. Please use `its(:version) { should eq '1.4.1' }` instead." + @version = version + end +end diff --git a/lib/resources/gem.rb b/lib/resources/gem.rb new file mode 100644 index 000000000..9333895dc --- /dev/null +++ b/lib/resources/gem.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 + +# Usage: +# describe gem('rubocop') do +# it { should be_installed } +# end +class GemPackage < Vulcano.resource(1) + name 'gem' + + def initialize(package_name) + @package_name = package_name + end + + def info + cmd = vulcano.run_command("gem list --local -a -q \^#{@package_name}\$") + return nil if cmd.exit_status != 0 + + # extract package name and version + # parses data like winrm (1.3.4, 1.3.3) + params = /^\s*([^\(]*?)\s*\((.*?)\)\s*$/.match(cmd.stdout.chomp) + versions = params[2].split(',') + @cache = { + name: params[1], + version: versions[0], + type: 'gem' + } + end + + def installed? + !info.nil? + end + + def version + return nil if info.nil? + info[:version] + end + + def to_s + "pip package #{@package_name}" + end +end diff --git a/lib/resources/npm.rb b/lib/resources/npm.rb new file mode 100644 index 000000000..4ebcfc385 --- /dev/null +++ b/lib/resources/npm.rb @@ -0,0 +1,39 @@ +# encoding: utf-8 + +# Usage: +# describe npm('bower') do +# it { should be_installed } +# end +class NpmPackage < Vulcano.resource(1) + name 'npm' + + def initialize(package_name) + @package_name = package_name + @cache = nil + end + + def info + return @cache if !@cache.nil? + cmd = vulcano.run_command("npm ls -g --json #{@package_name}") + return nil if cmd.exit_status != 0 + pkgs = JSON.parse(cmd.stdout) + @cache = { + name: @package_name, + version: pkgs['dependencies'][@package_name]['version'], + type: 'npm' + } + end + + def installed? + !info.nil? + end + + def version + return nil if info.nil? + info[:version] + end + + def to_s + "npm package #{@package_name}" + end +end diff --git a/lib/resources/package.rb b/lib/resources/package.rb new file mode 100644 index 000000000..86c71492a --- /dev/null +++ b/lib/resources/package.rb @@ -0,0 +1,136 @@ +# encoding: utf-8 + +# Resource to determine package information +# +# Usage: +# describe package('nginx') do +# it { should be_installed } +# end +class Package < Vulcano.resource(1) + name 'package' + + def initialize(package_name = nil) + @package_name = package_name + @name = @package_name + @cache = nil + + # select package manager + @pkgman = nil + case os[:family] + when 'ubuntu', 'debian' + @pkgman = Deb.new(vulcano) + when 'redhat', 'fedora' + @pkgman = Rpm.new(vulcano) + when 'arch' + @pkgman = Pacman.new(vulcano) + when 'darwin' + @pkgman = Brew.new(vulcano) + else + fail 'The `package` resource is not supported on your OS yet. Please open an issue on Github.' + end + end + + # returns true if the package is installed + def installed?(provider = nil, version = nil) + !info.nil? + end + + # returns the package description + def info + return @cache if !@cache.nil? + @pkgman.info(@package_name) + end + + # return the package version + def version + info = @pkgman.info(@package_name) + return nil if info.nil? + info[:version] + end + + def to_s + "System package #{@package_name}" + end +end + +class PkgManagement + def initialize(vulcano) + @vulcano = vulcano + end +end + +# Debian / Ubuntu +class Deb < PkgManagement + def info(package_name) + cmd = @vulcano.run_command("dpkg -s #{package_name}") + return nil if cmd.exit_status.to_i != 0 + + params = SimpleConfig.new( + cmd.stdout.chomp, + assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, + multiple_values: false + ).params + { + name: params['Package'], + installed: true, + version: params['Version'], + type: 'deb' + } + end +end + +# RHEL family +class Rpm < PkgManagement + def info(package_name) + cmd = @vulcano.run_command("rpm -qia #{package_name}") + return nil if cmd.exit_status.to_i != 0 + params = SimpleConfig.new( + cmd.stdout.chomp, + assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, + multiple_values: false + ).params + { + name: params['Name'], + installed: true, + version: params['Version'], + type: 'rpm' + } + end +end + +# MacOS / Darwin implementation +class Brew < PkgManagement + def info(package_name) + cmd = @vulcano.run_command("brew info --json=v1 #{package_name}") + return nil if cmd.exit_status.to_i != 0 + # parse data + pkg = JSON.parse(cmd.stdout)[0] + { + name: "#{pkg.name}", + installed: true, + version: "#{pkg.installed.version}", + type: 'brew' + } + end +end + +# Arch Linux +class Pacman < PkgManagement + def info(package_name) + cmd = @vulcano.run_command("pacman -Qi #{package_name}") + return nil if cmd.exit_status.to_i != 0 + + params = SimpleConfig.new( + cmd.stdout.chomp, + assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, + multiple_values: false + ).params + + { + name: params['Name'], + installed: true, + version: params['Version'], + type: 'pacman' + } + end +end diff --git a/lib/resources/pip.rb b/lib/resources/pip.rb new file mode 100644 index 000000000..94d7f4106 --- /dev/null +++ b/lib/resources/pip.rb @@ -0,0 +1,43 @@ +# encoding: utf-8 + +# Usage: +# describe pip('Jinja2') do +# it { should be_installed } +# end +class PipPackage < Vulcano.resource(1) + name 'pip' + + def initialize(package_name) + @package_name = package_name + end + + def info + cmd = vulcano.run_command("pip show #{@package_name}") + return nil if cmd.exit_status != 0 + + params = SimpleConfig.new( + cmd.stdout, + assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, + multiple_values: false + ).params + @cache = { + name: params['Name'], + installed: true, + version: params['Version'], + type: 'pip' + } + end + + def installed? + !info.nil? + end + + def version + return nil if info.nil? + info[:version] + end + + def to_s + "pip package #{@package_name}" + end +end diff --git a/lib/vulcano/resource.rb b/lib/vulcano/resource.rb index 8ab0d9961..b41d96233 100644 --- a/lib/vulcano/resource.rb +++ b/lib/vulcano/resource.rb @@ -27,6 +27,7 @@ require 'resources/command' require 'resources/directory' require 'resources/etc_group' require 'resources/file' +require 'resources/gem' require 'resources/group_policy' require 'resources/inetd_conf' require 'resources/limits_conf' @@ -34,10 +35,13 @@ require 'resources/login_def' require 'resources/mysql' require 'resources/mysql_conf' require 'resources/mysql_session' +require 'resources/npm' require 'resources/ntp_conf' require 'resources/os_env' +require 'resources/package' require 'resources/parse_config' require 'resources/passwd' +require 'resources/pip' require 'resources/postgres' require 'resources/postgres_conf' require 'resources/postgres_session' diff --git a/test/helper.rb b/test/helper.rb index bfc17c2fa..5775a2260 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -49,15 +49,21 @@ def loadResource (resource, *args) @backend.mock_command( stdout, '', 0 ) } @backend.commands = { - 'ps aux' => cmd.('ps-aux'), - 'type win_secpol.cfg' => cmd.('secedit-export'), - 'secedit /export /cfg win_secpol.cfg' => cmd.('success'), - 'del win_secpol.cfg' => cmd.('success'), - 'su - root -c \'echo $PATH\'' => cmd.('PATH'), - '(Get-Item \'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Schedule\').GetValue(\'Start\')' => cmd.('reg_schedule'), - 'Auditpol /get /subcategory:\'User Account Management\' /r' => cmd.('auditpol'), - '/sbin/auditctl -l' => cmd.('auditctl'), - 'yum -v repolist all' => cmd.('yum-repolist-all'), + 'ps aux' => cmd.call('ps-aux'), + 'type win_secpol.cfg' => cmd.call('secedit-export'), + 'secedit /export /cfg win_secpol.cfg' => cmd.call('success'), + 'del win_secpol.cfg' => cmd.call('success'), + 'su - root -c \'echo $PATH\'' => cmd.call('PATH'), + '(Get-Item \'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Schedule\').GetValue(\'Start\')' => cmd.call('reg_schedule'), + 'Auditpol /get /subcategory:\'User Account Management\' /r' => cmd.call('auditpol'), + '/sbin/auditctl -l' => cmd.call('auditctl'), + 'yum -v repolist all' => cmd.call('yum-repolist-all'), + 'dpkg -s curl' => cmd.call('dpkg-s-curl'), + 'rpm -qia curl' => cmd.call('rpm-qia-curl'), + 'pacman -Qi curl' => cmd.call('packman-qi-curl'), + 'gem list --local -a -q ^rubocop$' => cmd.call('gem-list-local-a-q-rubocop'), + 'npm ls -g --json bower' => cmd.call('npm-ls-g--json-bower'), + 'pip show jinja2' => cmd.call('pip-show-jinja2') } # load resource diff --git a/test/unit/mock/cmd/brew-info-jq b/test/unit/mock/cmd/brew-info-jq new file mode 100644 index 000000000..3faff22f0 --- /dev/null +++ b/test/unit/mock/cmd/brew-info-jq @@ -0,0 +1 @@ +[{"name":"jq","full_name":"jq","desc":"Lightweight and flexible command-line JSON processor","homepage":"https://stedolan.github.io/jq/","versions":{"stable":"1.4","bottle":true,"devel":"1.5rc2","head":"HEAD"},"revision":0,"installed":[{"version":"1.4","used_options":[],"built_as_bottle":null,"poured_from_bottle":true}],"linked_keg":"1.4","keg_only":null,"dependencies":["bison"],"conflicts_with":[],"caveats":null,"requirements":[],"options":[]}] diff --git a/test/unit/mock/cmd/dpkg-s-curl b/test/unit/mock/cmd/dpkg-s-curl new file mode 100644 index 000000000..7bafc71ae --- /dev/null +++ b/test/unit/mock/cmd/dpkg-s-curl @@ -0,0 +1,21 @@ +Package: curl +Status: install ok installed +Priority: optional +Section: web +Installed-Size: 306 +Maintainer: Ubuntu Developers +Architecture: amd64 +Multi-Arch: foreign +Version: 7.35.0-1ubuntu2 +Depends: libc6 (>= 2.17), libcurl3 (= 7.35.0-1ubuntu2), zlib1g (>= 1:1.1.4) +Description: command line tool for transferring data with URL syntax + curl is a command line tool for transferring data with URL syntax, supporting + DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, + POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP. + . + curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form + based upload, proxies, cookies, user+password authentication (Basic, Digest, + NTLM, Negotiate, kerberos...), file transfer resume, proxy tunneling and a + busload of other useful tricks. +Homepage: http://curl.haxx.se +Original-Maintainer: Alessandro Ghedini diff --git a/test/unit/mock/cmd/gem-list-local-a-q-rubocop b/test/unit/mock/cmd/gem-list-local-a-q-rubocop new file mode 100644 index 000000000..3e2b6810d --- /dev/null +++ b/test/unit/mock/cmd/gem-list-local-a-q-rubocop @@ -0,0 +1 @@ +rubocop (0.33.0, 0.32.1, 0.28.0) diff --git a/test/unit/mock/cmd/npm-ls-g--json-bower b/test/unit/mock/cmd/npm-ls-g--json-bower new file mode 100644 index 000000000..a7ee4566c --- /dev/null +++ b/test/unit/mock/cmd/npm-ls-g--json-bower @@ -0,0 +1,9 @@ +{ + "dependencies": { + "bower": { + "version": "1.4.1", + "from": "bower@*", + "resolved": "https://registry.npmjs.org/bower/-/bower-1.4.1.tgz" + } + } +} diff --git a/test/unit/mock/cmd/packman-qi-curl b/test/unit/mock/cmd/packman-qi-curl new file mode 100644 index 000000000..f21159bc3 --- /dev/null +++ b/test/unit/mock/cmd/packman-qi-curl @@ -0,0 +1,21 @@ +Name : curl +Version : 7.37.0-1 +Description : An URL retrieval utility and library +Architecture : x86_64 +URL : http://curl.haxx.se +Licenses : MIT +Groups : None +Provides : libcurl.so=4-64 +Depends On : ca-certificates krb5 libssh2 openssl zlib +Optional Deps : None +Required By : pacman +Optional For : gnupg +Conflicts With : None +Replaces : None +Installed Size : 962.00 KiB +Packager : Dave Reisner +Build Date : Wed May 21 17:55:21 2014 +Install Date : Mon Jul 21 01:15:48 2014 +Install Reason : Installed as a dependency for another package +Install Script : No +Validated By : Signature diff --git a/test/unit/mock/cmd/pip-show-jinja2 b/test/unit/mock/cmd/pip-show-jinja2 new file mode 100644 index 000000000..4b0fb50c5 --- /dev/null +++ b/test/unit/mock/cmd/pip-show-jinja2 @@ -0,0 +1,11 @@ +--- +Metadata-Version: 2.0 +Name: Jinja2 +Version: 2.8 +Summary: A small but fast and easy to use stand-alone template engine written in pure python. +Home-page: http://jinja.pocoo.org/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +License: BSD +Location: /Library/Python/2.7/site-packages +Requires: MarkupSafe diff --git a/test/unit/mock/cmd/rpm-qia-curl b/test/unit/mock/cmd/rpm-qia-curl new file mode 100644 index 000000000..816bcc87e --- /dev/null +++ b/test/unit/mock/cmd/rpm-qia-curl @@ -0,0 +1,24 @@ +Name : curl +Version : 7.29.0 +Release : 19.el7 +Architecture: x86_64 +Install Date: Thu Jun 18 12:38:01 2015 +Group : Applications/Internet +Size : 535105 +License : MIT +Signature : RSA/SHA256, Fri Jul 4 01:04:23 2014, Key ID 24c6a8a7f4a80eb5 +Source RPM : curl-7.29.0-19.el7.src.rpm +Build Date : Mon Jun 9 23:19:16 2014 +Build Host : worker1.bsys.centos.org +Relocations : (not relocatable) +Packager : CentOS BuildSystem +Vendor : CentOS +URL : http://curl.haxx.se/ +Summary : A utility for getting files from remote servers (FTP, HTTP, and others) +Description : +curl is a command line tool for transferring data with URL syntax, supporting +FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, IMAP, +SMTP, POP3 and RTSP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP +uploading, HTTP form based upload, proxies, cookies, user+password +authentication (Basic, Digest, NTLM, Negotiate, kerberos...), file transfer +resume, proxy tunneling and a busload of other useful tricks. diff --git a/test/unit/resource_gem_test.rb b/test/unit/resource_gem_test.rb new file mode 100644 index 000000000..e086082db --- /dev/null +++ b/test/unit/resource_gem_test.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 + +require 'helper' +require 'vulcano/resource' + +describe 'Vulcano::Resources::Passwd' do + describe 'gem' do + let(:resource) { loadResource('gem', 'rubocop') } + + it 'verify gem package detail parsing' do + pkg = { name: 'rubocop', version: '0.33.0', type: 'gem' } + _(resource.installed?).must_equal true + _(resource.info).must_equal pkg + end + + end +end diff --git a/test/unit/resource_npm_test.rb b/test/unit/resource_npm_test.rb new file mode 100644 index 000000000..870b5ffbf --- /dev/null +++ b/test/unit/resource_npm_test.rb @@ -0,0 +1,16 @@ +# encoding: utf-8 + +require 'helper' +require 'vulcano/resource' + +describe 'Vulcano::Resources::Passwd' do + describe 'npm' do + let(:resource) { loadResource('npm', 'bower') } + + it 'verify npm package detail parsing' do + pkg = { name: 'bower', version: '1.4.1', type: 'npm'} + _(resource.installed?).must_equal true + _(resource.info).must_equal pkg + end + end +end diff --git a/test/unit/resource_package_test.rb b/test/unit/resource_package_test.rb new file mode 100644 index 000000000..19a0198e6 --- /dev/null +++ b/test/unit/resource_package_test.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 + +require 'helper' +require 'vulcano/resource' + +describe 'Vulcano::Resources::Passwd' do + describe 'package' do + + # TODO: set operating system to arch + # let(:resource) { loadResource('package', 'curl') } + + # arch linux + # it 'verify package parsing' do + # pkg = { name: 'curl', installed: true, version: '7.37.0-1', type: 'pacman' } + # _(resource.installed?).must_equal true + # _(resource.info).must_equal pkg + # end + + # ubuntu + # it 'verify package parsing' do + # pkg = { name: 'curl', installed: true, version: '7.35.0-1ubuntu2', type: 'deb' } + # _(resource.installed?).must_equal true + # _(resource.info).must_equal pkg + # end + + # centos + # + # it 'verify package parsing' do + # pkg = { name: 'curl', installed: true, version: '7.29.0', type: 'rpm' } + # _(resource.installed?).must_equal true + # _(resource.info).must_equal pkg + # end + + end +end diff --git a/test/unit/resource_pip_test.rb b/test/unit/resource_pip_test.rb new file mode 100644 index 000000000..bd8515fce --- /dev/null +++ b/test/unit/resource_pip_test.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 + +require 'helper' +require 'vulcano/resource' + +describe 'Vulcano::Resources::Passwd' do + describe 'pip' do + let(:resource) { loadResource('pip', 'jinja2') } + + it 'verify pip package detail parsing' do + pkg = {:name=>"Jinja2", :installed=>true, :version=>"2.8", :type=>"pip"} + _(resource.installed?).must_equal true + _(resource.info).must_equal pkg + end + + end +end