diff --git a/Gemfile b/Gemfile index 35f2a4f..51d46d8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,54 +1,56 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end group :test do gem 'puppetlabs_spec_helper', '~> 1.2.2', :require => false gem 'rspec-puppet', :require => false, :git => 'https://github.com/rodjek/rspec-puppet.git' gem 'rspec-puppet-facts', :require => false gem 'rspec-puppet-utils', :require => false gem 'puppet-lint-absolute_classname-check', :require => false gem 'puppet-lint-leading_zero-check', :require => false gem 'puppet-lint-trailing_comma-check', :require => false gem 'puppet-lint-version_comparison-check', :require => false gem 'puppet-lint-classes_and_types_beginning_with_digits-check', :require => false gem 'puppet-lint-unquoted_string-check', :require => false gem 'puppet-lint-variable_contains_upcase', :require => false gem 'metadata-json-lint', :require => false gem 'puppet-strings', '1.1.0', :require => false gem 'puppet_facts', :require => false gem 'rubocop-rspec', '~> 1.6', :require => false if RUBY_VERSION >= '2.3.0' gem 'json_pure', '<= 2.0.1', :require => false if RUBY_VERSION < '2.0.0' gem 'safe_yaml', '~> 1.0.4', :require => false gem 'listen', '<= 3.0.6', :require => false gem 'puppet-syntax', :require => false, git: 'https://github.com/gds-operations/puppet-syntax.git' gem 'pry' gem 'rb-readline' gem 'redis', :require => false gem 'mock_redis', :require => false end group :development do gem 'puppet-blacksmith' gem 'travis' gem 'github_changelog_generator' end group :system_tests do gem "beaker", '2.41.0', :require => false gem "beaker-rspec", '5.6.0', :require => false gem 'beaker-puppet_install_helper', :require => false + gem 'beaker-module_install_helper' + gem 'vagrant-wrapper' end ENV['PUPPET_GEM_VERSION'].nil? ? puppetversion = '~> 4.0' : puppetversion = ENV['PUPPET_GEM_VERSION'].to_s gem 'puppet', puppetversion, :require => false, :groups => [:test] # vim: syntax=ruby diff --git a/spec/acceptance/nodesets/centos-6-docker.yml b/spec/acceptance/nodesets/centos-6-docker.yml index 57a91b9..21462c1 100644 --- a/spec/acceptance/nodesets/centos-6-docker.yml +++ b/spec/acceptance/nodesets/centos-6-docker.yml @@ -1,11 +1,13 @@ HOSTS: centos-6-x64: + roles: + - master platform: el-6-x86_64 hypervisor : docker image: petems/docker-centos-6-ssh-locale:centos-6 docker_preserve_image: true docker_cmd: '["/sbin/init"]' docker_preserve_image: true CONFIG: type: foss log_level: debug diff --git a/spec/acceptance/nodesets/centos-7-docker.yml b/spec/acceptance/nodesets/centos-7-docker.yml index 443b9b8..6eae300 100644 --- a/spec/acceptance/nodesets/centos-7-docker.yml +++ b/spec/acceptance/nodesets/centos-7-docker.yml @@ -1,11 +1,13 @@ HOSTS: centos-7-x64: + roles: + - master platform: el-7-x86_64 hypervisor : docker image: petems/docker-centos-7-ssh-locale:centos-7 docker_preserve_image: true docker_cmd: '["/sbin/init"]' docker_preserve_image: true CONFIG: type: foss log_level: debug diff --git a/spec/acceptance/nodesets/ubuntu-1604-docker.yml b/spec/acceptance/nodesets/ubuntu-1604-docker.yml index 00e195e..7f29a22 100644 --- a/spec/acceptance/nodesets/ubuntu-1604-docker.yml +++ b/spec/acceptance/nodesets/ubuntu-1604-docker.yml @@ -1,13 +1,15 @@ HOSTS: ubuntu-16-04: + roles: + - master platform: ubuntu-16.04-amd64 image: ubuntu:16.04 hypervisor: docker docker_cmd: '["/sbin/init"]' docker_image_commands: - 'apt-get install -y net-tools wget curl locales apt-transport-https software-properties-common' - 'locale-gen en en_US en_US.UTF-8' docker_preserve_image: true CONFIG: type: foss log_level: debug diff --git a/spec/acceptance/redis_cli_task_spec.rb b/spec/acceptance/redis_cli_task_spec.rb new file mode 100644 index 0000000..d9f2f8f --- /dev/null +++ b/spec/acceptance/redis_cli_task_spec.rb @@ -0,0 +1,37 @@ +# run a test task +require 'spec_helper_acceptance' + +describe 'redis-cli task' do + + it 'install redis-cli with the class' do + pp = <<-EOS + Exec { + path => [ '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', ] + } + + class { '::redis': + manage_repo => true, + } + EOS + + # Apply twice to ensure no errors the second time. + apply_manifest(pp, :catch_failures => true) + end + + describe 'ping' do + it 'execute ping' do + result = run_task(task_name: 'redis::redis_cli', params: 'command="ping"') + expect_multiple_regexes(result: result, regexes: [%r{{"status":"PONG"}}, %r{Ran on 1 node in .+ seconds}]) + end + end + + describe 'security' do + it 'stops script injections and escapes' do + result = run_task(task_name: 'redis::redis_cli', params: 'command="ping; cat /etc/passwd"') + expect_multiple_regexes(result: result, regexes: [%r{{"status":"ERR unknown command 'ping; cat /etc/passwd'"}}, %r{Ran on 1 node in .+ seconds}]) + + result = run_task(task_name: 'redis::redis_cli', params: 'command="ping && cat /etc/passwd"') + expect_multiple_regexes(result: result, regexes: [%r{{"status":"ERR unknown command 'ping && cat /etc/passwd'"}}, %r{Ran on 1 node in .+ seconds}]) + end + end +end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 737409c..21b3c51 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -1,30 +1,87 @@ require 'beaker-rspec' require 'beaker/puppet_install_helper' +require 'beaker/module_install_helper' + +def change_root_password + on(hosts, 'echo "root:root" | chpasswd') +end + +def install_bolt_on(hosts) + on(hosts, "/opt/puppetlabs/puppet/bin/gem install bolt -v '0.5.1' --no-ri --no-rdoc", acceptable_exit_codes: [0]).stdout +end run_puppet_install_helper unless ENV['BEAKER_provision'] == 'no' +change_root_password +install_module_on(hosts) +install_module_dependencies_on(hosts) + +UNSUPPORTED_PLATFORMS = %w[windows AIX Solaris].freeze + +DEFAULT_PASSWORD = if default[:hypervisor] == 'vagrant' + 'root' + elsif default[:hypervisor] == 'docker' + 'root' + end + +def run_task(task_name:, params: nil, password: DEFAULT_PASSWORD) + run_bolt_task(task_name: task_name, params: params, password: password) +end + +def run_bolt_task(task_name:, params: nil, password: DEFAULT_PASSWORD) + on(master, "/opt/puppetlabs/puppet/bin/bolt task run #{task_name} --modules /etc/puppetlabs/code/modules/ --nodes localhost --user root --password #{password} #{params}", acceptable_exit_codes: [0, 1]).stdout # rubocop:disable Metrics/LineLength +end + +def expect_multiple_regexes(result:, regexes:) + regexes.each do |regex| + expect(result).to match(regex) + end +end + +# This method allows a block to be passed in and if an exception is raised +# that matches the 'error_matcher' matcher, the block will wait a set number +# of seconds before retrying. +# Params: +# - max_retry_count - Max number of retries +# - retry_wait_interval_secs - Number of seconds to wait before retry +# - error_matcher - Matcher which the exception raised must match to allow retry +# Example Usage: +# retry_on_error_matching(3, 5, /OpenGPG Error/) do +# apply_manifest(pp, :catch_failures => true) +# end +def retry_on_error_matching(max_retry_count = 3, retry_wait_interval_secs = 5, error_matcher = nil) + try = 0 + begin + try += 1 + yield + rescue Exception => e + if try < max_retry_count && (error_matcher.nil? || e.message =~ error_matcher) + sleep retry_wait_interval_secs + retry + else + raise + end + end +end RSpec.configure do |c| # Project root proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) # Readable test descriptions c.formatter = :documentation c.before :suite do - # Install module and dependencies - puppet_module_install(:source => proj_root, :module_name => 'redis') hosts.each do |host| if fact('osfamily') == 'Debian' # These should be on all Deb-flavor machines by default... # But Docker is often more slimline - shell('apt-get install apt-transport-https software-properties-common -y', { :acceptable_exit_codes => [0] }) + shell('apt-get install apt-transport-https software-properties-common -y', acceptable_exit_codes: [0]) end - on host, puppet('module', 'install', 'puppetlabs-stdlib -v 4.11.0'), { :acceptable_exit_codes => [0] } - on host, puppet('module', 'install', 'puppetlabs-apt -v 2.3.0'), { :acceptable_exit_codes => [0] } - on host, puppet('module', 'install', 'stahnma-epel -v 1.2.2'), { :acceptable_exit_codes => [0] } - on host, puppet('module', 'install', 'herculesteam/augeasproviders_core -v 2.1.0'), { :acceptable_exit_codes => [0] } - on host, puppet('module', 'install', 'herculesteam/augeasproviders_sysctl -v 2.1.0'), { :acceptable_exit_codes => [0] } + # Bolt requires gcc and make + install_package(host, 'gcc') + install_package(host, 'make') + install_bolt_on(host) end end end diff --git a/tasks/redis_cli.json b/tasks/redis_cli.json new file mode 100644 index 0000000..9999dac --- /dev/null +++ b/tasks/redis_cli.json @@ -0,0 +1,10 @@ +{ + "description": "Executes a redis-cli command on the target system", + "input_method": "stdin", + "parameters": { + "command": { + "description": "The command to run, including all arguments", + "type": "String[1]" + } + } +} diff --git a/tasks/redis_cli.rb b/tasks/redis_cli.rb new file mode 100755 index 0000000..1d848a2 --- /dev/null +++ b/tasks/redis_cli.rb @@ -0,0 +1,22 @@ +#!/opt/puppetlabs/puppet/bin/ruby +require 'json' +require 'open3' +require 'puppet' + +def redis_cli(command) + stdout, stderr, status = Open3.capture3("redis-cli", command) + raise Puppet::Error, stderr if status != 0 + { status: stdout.strip } +end + +params = JSON.parse(STDIN.read) +command = params['command'] + +begin + result = redis_cli(command) + puts result.to_json + exit 0 +rescue Puppet::Error => e + puts({ status: 'failure', error: e.message }.to_json) + exit 1 +end