From 7d7fbe3590c1a469ac3c71b1cc97df70314b1e68 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Wed, 24 Jan 2018 15:01:07 -0800 Subject: [PATCH] (BKR-1342) Import ci rake tasks and install utils This commit pulls all the ci acceptance rake tasks and install/setup utilities previously defined in puppet into beaker-puppet. --- lib/beaker-puppet.rb | 6 + lib/beaker-puppet/helpers/rake_helpers.rb | 21 + lib/beaker-puppet/helpers/setup/aio.rb | 105 +++++ lib/beaker-puppet/helpers/setup/common.rb | 187 +++++++++ lib/beaker-puppet/helpers/setup/gem.rb | 54 +++ lib/beaker-puppet/helpers/setup/git.rb | 469 ++++++++++++++++++++++ lib/beaker-puppet/helpers/setup/pe.rb | 39 ++ tasks/ci.rake | 246 ++++++++++++ 8 files changed, 1127 insertions(+) create mode 100644 lib/beaker-puppet/helpers/rake_helpers.rb create mode 100644 lib/beaker-puppet/helpers/setup/aio.rb create mode 100644 lib/beaker-puppet/helpers/setup/common.rb create mode 100644 lib/beaker-puppet/helpers/setup/gem.rb create mode 100644 lib/beaker-puppet/helpers/setup/git.rb create mode 100644 lib/beaker-puppet/helpers/setup/pe.rb create mode 100644 tasks/ci.rake diff --git a/lib/beaker-puppet.rb b/lib/beaker-puppet.rb index 0f31a491..bbd9d653 100644 --- a/lib/beaker-puppet.rb +++ b/lib/beaker-puppet.rb @@ -3,6 +3,8 @@ require 'beaker-puppet/version' require 'beaker-puppet/wrappers' +require 'beaker-puppet/helpers/rake_helpers' + [ 'aio', 'foss' ].each do |lib| require "beaker-puppet/install_utils/#{lib}_defaults" end @@ -12,6 +14,9 @@ [ 'tk', 'facter', 'puppet' ].each do |lib| require "beaker-puppet/helpers/#{lib}_helpers" end +['aio', 'common', 'gem', 'git', 'pe'].each do |lib| + require "beaker-puppet/helpers/setup/#{lib}" +end require 'beaker-puppet/install_utils/puppet5' @@ -33,6 +38,7 @@ module Helpers include Beaker::DSL::Helpers::TKHelpers include Beaker::DSL::Helpers::FacterHelpers include Beaker::DSL::Helpers::PuppetHelpers + include Beaker::DSL::Helpers::RakeHelpers end include Beaker::DSL::Wrappers diff --git a/lib/beaker-puppet/helpers/rake_helpers.rb b/lib/beaker-puppet/helpers/rake_helpers.rb new file mode 100644 index 00000000..bdd6209c --- /dev/null +++ b/lib/beaker-puppet/helpers/rake_helpers.rb @@ -0,0 +1,21 @@ +module Beaker + module DSL + module Helpers + # Methods that help you interact with rake during ci setup + module RakeHelpers + class << self + def load_tasks(beaker_root = '/Users/melissa/beaker-puppet') + task_dir = File.join(beaker_root, 'tasks') + tasks = [ + 'ci.rake' + ] + + tasks.each do |task| + load File.join(task_dir, task) + end + end + end + end + end + end +end diff --git a/lib/beaker-puppet/helpers/setup/aio.rb b/lib/beaker-puppet/helpers/setup/aio.rb new file mode 100644 index 00000000..4eda1d36 --- /dev/null +++ b/lib/beaker-puppet/helpers/setup/aio.rb @@ -0,0 +1,105 @@ +module Beaker + module DSL + module Helpers + module Setup + module AIO + require 'beaker-puppet/helpers/setup/common' + require 'beaker-puppet/install_utils/puppet5' + + include BeakerPuppet::Install::Puppet5 + include Beaker::DSL::Helpers::Setup::Common + + def aio_install + test_name "Installl Packages" do + dev_builds_url = ENV['DEV_BUILDS_URL'] || 'http://builds.delivery.puppetlabs.net' + sha = ENV['SHA'] + server_version = ENV['SERVER_VERSION'] + + step "Install puppet-agent..." do + install_from_build_data_url('puppet-agent', "#{dev_builds_url}/puppet-agent/#{sha}/artifacts/#{sha}.yaml", hosts) + end + + step "Install puppetserver..." do + install_from_build_data_url('puppetserver', "#{dev_builds_url}/puppetserver/#{server_version}/artifacts/#{server_version}.yaml", master) + end + + # make sure install is sane, beaker has already added puppet and ruby + # to PATH in ~/.ssh/environment + hosts.each do |host| + on host, puppet('--version') + ruby = ruby_command(host) + on host, "#{ruby} --version" + end + + # Get a rough estimate of clock skew among hosts + times = [] + hosts.each do |host| + ruby = ruby_command(host) + on(host, "#{ruby} -e 'puts Time.now.strftime(\"%Y-%m-%d %T.%L %z\")'") do |result| + times << result.stdout.chomp + end + end + times.map! do |time| + (Time.strptime(time, "%Y-%m-%d %T.%L %z").to_f * 1000.0).to_i + end + diff = times.max - times.min + if diff < 60000 + logger.info "Host times vary #{diff} ms" + else + logger.warn "Host times vary #{diff} ms, tests may fail" + end + + configure_gem_mirror(hosts) + end + end + + def install_cumulus_modules + test_name "Install Cumulus Modules" do + platforms = hosts.map{|val| val[:platform]} + skip_test "No cumulus hosts present" unless platforms.any? { |val| /cumulus/ =~ val } + confine :to, {}, hosts.select { |host| host[:roles].include?('master') } + + step 'install Cumulus Modules on masters' do + hosts.each do |node| + on(node, puppet('module','install','cumuluslinux-cumulus_license')) + on(node, puppet('module','install','cumuluslinux-cumulus_interfaces')) + on(node, puppet('module','install','cumuluslinux-cumulus_interface_policy')) + on(node, puppet('module','install','cumuluslinux-cumulus_ports')) + end + end + end + end + + def install_arista_module + platforms = hosts.map{|val| val[:platform]} + skip_test "No arista hosts present" unless platforms.any? { |val| /^eos-/ =~ val } + test_name 'Arista Switch Pre-suite' do + masters = select_hosts({:roles => ['master', 'compile_master']}) + switchs = select_hosts({:platform => ['eos-4-i386']}) + + step 'install Arista Module on masters' do + masters.each do |node| + on(node, puppet('module','install','aristanetworks-netdev_stdlib_eos')) + end + end + + step 'add puppet user to switch' do + switchs.each do |switch| + on(switch, "useradd -U puppet") + on(switch, "/opt/puppetlabs/bin/puppet config --confdir /etc/puppetlabs/puppet set user root") + on(switch, "/opt/puppetlabs/bin/puppet config --confdir /etc/puppetlabs/puppet set group root") + end + end + end + end + + def ensure_master_started + test_name 'Ensure the master service is running' do + on(master, puppet('resource', 'service', master['puppetservice'], "ensure=running")) + end + end + end + end + end + end +end diff --git a/lib/beaker-puppet/helpers/setup/common.rb b/lib/beaker-puppet/helpers/setup/common.rb new file mode 100644 index 00000000..e7d5979e --- /dev/null +++ b/lib/beaker-puppet/helpers/setup/common.rb @@ -0,0 +1,187 @@ +module Beaker + module DSL + module Helpers + module Setup + module Common + + # Installs packages on the hosts. + # + # @param hosts [Array] Array of hosts to install packages to. + # @param package_hash [Hash{Symbol=>Array>}] + # Keys should be a symbol for a platform in PLATFORM_PATTERNS. Values + # should be an array of package names to install, or of two element + # arrays where a[0] is the command we expect to find on the platform + # and a[1] is the package name (when they are different). + # @param options [Hash{Symbol=>Boolean}] + # @option options [Boolean] :check_if_exists First check to see if + # command is present before installing package. (Default false) + # @return true + def install_packages_on(hosts, package_hash, options = {}) + platform_patterns = { + :redhat => /fedora|el-|centos/, + :debian => /debian|ubuntu|cumulus/, + :debian_ruby18 => /debian|ubuntu-lucid|ubuntu-precise/, + :solaris_10 => /solaris-10/, + :solaris_11 => /solaris-11/, + :windows => /windows/, + :eos => /^eos-/, + }.freeze + + check_if_exists = options[:check_if_exists] + Array(hosts).each do |host| + package_hash.each do |platform_key,package_list| + if pattern = platform_patterns[platform_key] + if pattern.match(host['platform']) + package_list.each do |cmd_pkg| + if cmd_pkg.kind_of?(Array) + command, package = cmd_pkg + else + command = package = cmd_pkg + end + if !check_if_exists || !host.check_for_package(command) + host.logger.notify("Installing #{package}") + additional_switches = '--allow-unauthenticated' if platform_key == :debian + host.install_package(package, additional_switches) + end + end + end + else + raise("Unknown platform '#{platform_key}' in package_hash") + end + end + end + return true + end + + def ruby_command(host) + "env PATH=\"#{host['privatebindir']}:${PATH}\" ruby" + end + + def gem_command(host, type = 'aio') + if type == 'aio' + if host['platform'] =~ /windows/ + "env PATH=\"#{host['privatebindir']}:${PATH}\" cmd /c gem" + else + "env PATH=\"#{host['privatebindir']}:${PATH}\" gem" + end + else + on(host, 'which gem').stdout.chomp + end + end + + # Configures gem sources on hosts to use a mirror, if specified + # This is a duplicate of the Gemfile logic. + def configure_gem_mirror(hosts) + hosts = Array(hosts) + gem_source = ENV['GEM_SOURCE'] || 'https://rubygems.org' + + hosts.each do |host| + gem = gem_command(host) + on host, "#{gem} source --clear-all" + on host, "#{gem} source --add #{gem_source}" + end + end + + # Ensure that the any previous installations of puppet + # are removed from the host if it is not managed by a + # provisioning hypervisor. + def delete_puppet_when_none + test_name "Expunge puppet bits if hypervisor is none" do + hosts.each do |host| + if host[:hypervisor] == "none" + remove_puppet_on(host) + end + end + end + end + + def stop_firewall + test_name "Stop firewall" do + hosts.each do |host| + case host['platform'] + when /debian/ + on host, 'iptables -F' + when /fedora|el-7/ + on host, puppet('resource', 'service', 'firewalld', 'ensure=stopped') + when /el-|centos/ + on host, puppet('resource', 'service', 'iptables', 'ensure=stopped') + when /ubuntu/ + on host, puppet('resource', 'service', 'ufw', 'ensure=stopped') + else + logger.notify("Not sure how to clear firewall on #{host['platform']}") + end + end + end + end + + # The sssd service causes local users/groups to be cached, + # which can cause unexpected results when tests are trying + # to restore state. We ensure that it is not running to + # prevent such caching from occurring. + def stop_sssd(hosts) + test_name "Stop sssd" do + hosts.each do |host| + on(host, puppet('resource', 'service', 'sssd', 'ensure=stopped'), :accept_all_exit_codes => true) + end + end + end + + def validate_sign_cert(hosts) + test_name "Validate Sign Cert" do + hostname = on(master, 'facter hostname').stdout.strip + fqdn = on(master, 'facter fqdn').stdout.strip + + if master.use_service_scripts? + step "Ensure puppet is stopped" + # Passenger, in particular, must be shutdown for the cert setup steps to work, + # but any running puppet master will interfere with webrick starting up and + # potentially ignore the puppet.conf changes. + on(master, puppet('resource', 'service', master['puppetservice'], "ensure=stopped")) + end + + step "Clear SSL on all hosts" + hosts.each do |host| + ssldir = on(host, puppet('agent --configprint ssldir')).stdout.chomp + on(host, "rm -rf '#{ssldir}'") + end + + step "Master: Start Puppet Master" + master_opts = { + :main => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + }, + :__service_args__ => { + # apache2 service scripts can't restart if we've removed the ssl dir + :bypass_service_script => true, + }, + } + with_puppet_running_on(master, master_opts) do + + hosts.each do |host| + next if host['roles'].include? 'master' + + step "Agents: Run agent --test first time to gen CSR" + on host, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [1] + end + + # Sign all waiting certs + step "Master: sign all certs" + on master, puppet("cert --sign --all"), :acceptable_exit_codes => [0,24] + + step "Agents: Run agent --test second time to obtain signed cert" + on agents, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [0,2] + end + end + end + + # TODO I don't believe this is used anywhere + #def set_pe_puppet_service + # test_name "Set puppetservice to pe-puppetserver" do + # master['puppetservice'] = 'pe-puppetserver' + # end + #end + end + end + end + end +end diff --git a/lib/beaker-puppet/helpers/setup/gem.rb b/lib/beaker-puppet/helpers/setup/gem.rb new file mode 100644 index 00000000..d794dbf7 --- /dev/null +++ b/lib/beaker-puppet/helpers/setup/gem.rb @@ -0,0 +1,54 @@ +module Beaker + module DSL + module Helpers + module Setup + module Gem + + require 'beaker-puppet/helpers/setup/common' + include Beaker::DSL::Helpers::Setup::Common + + def gem_install + test_name "Install puppet gem" do + agents.each do |agent| + sha = ENV['SHA'] + base_url = "http://builds.delivery.puppetlabs.net/puppet/#{sha}/artifacts" + + ruby = ruby_command(agent) + gem = gem_command(agent) + + # retrieve the build data, since the gem version is based on the short git + # describe, not the full git SHA + on(agent, "curl -s -o build_data.yaml #{base_url}/#{sha}.yaml") + gem_version = on(agent, "#{ruby} -ryaml -e 'puts YAML.load_file(\"build_data.yaml\")[:gemversion]'").stdout.chomp + + if agent['platform'] =~ /windows/ + # wipe existing gems first + default_dir = on(agent, "#{ruby} -rrbconfig -e 'puts Gem.default_dir'").stdout.chomp + on(agent, "rm -rf '#{default_dir}'") + + arch = agent[:ruby_arch] || 'x86' + gem_arch = arch == 'x64' ? 'x64-mingw32' : 'x86-mingw32' + url = "#{base_url}/puppet-#{gem_version}-#{gem_arch}.gem" + else + url = "#{base_url}/puppet-#{gem_version}.gem" + end + + step "Download puppet gem from #{url}" + on(agent, "curl -s -o puppet.gem #{url}") + + step "Install puppet.gem" + on(agent, "#{gem} install puppet.gem") + + step "Verify it's sane" + on(agent, puppet('--version')) + on(agent, puppet('apply', "-e \"notify { 'hello': }\"")) do |result| + assert_match(/defined 'message' as 'hello'/, result.stdout) + end + end + end + end + end + end + end + end +end diff --git a/lib/beaker-puppet/helpers/setup/git.rb b/lib/beaker-puppet/helpers/setup/git.rb new file mode 100644 index 00000000..c40ecbb9 --- /dev/null +++ b/lib/beaker-puppet/helpers/setup/git.rb @@ -0,0 +1,469 @@ +module Beaker + module DSL + module Helpers + module Setup + module Git + + require 'beaker-puppet/helpers/setup/common' + require 'beaker-puppet/install_utils/aio_defaults' + include Beaker::DSL::Helpers::Setup::Common + include Beaker::DSL::InstallUtils::AIODefaults + + def env_setup + test_name "Setup environment" do + step "Ensure Git and Ruby" + + packages = { + :redhat => [ + 'git', + 'ruby', + 'rubygem-json', # invalid on RHEL6 + 'rubygem-io-console', # required for Fedora25 to bundle install + 'rubygem-rdoc' # required for Fedora25 to install gems + ], + :debian => [ + ['git', 'git-core'], + 'ruby', + ], + :debian_ruby18 => [ + 'libjson-ruby', + ], + :solaris_11 => [ + ['git', 'developer/versioning/git'], + ], + :solaris_10 => [ + 'coreutils', + 'curl', # update curl to fix "CURLOPT_SSL_VERIFYHOST no longer supports 1 as value!" issue + 'git', + 'ruby19', + 'ruby19_dev', + 'gcc4core', + ], + :windows => [ + 'git', + # there isn't a need for json on windows because it is bundled in ruby 1.9 + ], + } + + # override incorrect FOSS (git) defaults from Beaker with AIO applicable ones + # + # Remove after PUP-4867 breaks distmoduledir and sitemoduledir into individual + # settings from modulepath and Beaker can properly introspect these settings + hosts.each do |host| + platform = host['platform'] =~ /windows/ ? 'windows' : 'unix' + + host['puppetbindir'] = '/usr/bin' if platform == 'windows' + + # Beakers add_aio_defaults_on helper is not appropriate here as it + # also alters puppetbindir / privatebindir to use package installed + # paths rather than git installed paths + host['distmoduledir'] = AIO_DEFAULTS[platform]['distmoduledir'] + host['sitemoduledir'] = AIO_DEFAULTS[platform]['sitemoduledir'] + end + + hosts.each do |host| + case host['platform'] + when /solaris-10/ + on host, 'mkdir -p /var/lib' + on host, 'ln -sf /opt/csw/bin/pkgutil /usr/bin/pkgutil' + on host, 'ln -sf /opt/csw/bin/gem19 /usr/bin/gem' + on host, 'ln -sf /opt/csw/bin/git /usr/bin/git' + on host, 'ln -sf /opt/csw/bin/ruby19 /usr/bin/ruby' + on host, 'ln -sf /opt/csw/bin/gstat /usr/bin/stat' + on host, 'ln -sf /opt/csw/bin/greadlink /usr/bin/readlink' + when /solaris-11/ + step "#{host} jump through hoops to install ruby19; switch back to runtime/ruby-19 after template upgrade to sol11.2" + create_remote_file host, "/root/shutupsolaris", <<-END + mail= + # Overwrite already installed instances + instance=overwrite + # Do not bother checking for partially installed packages + partial=nocheck + # Do not bother checking the runlevel + runlevel=nocheck + # Do not bother checking package dependencies (We take care of this) + idepend=nocheck + rdepend=nocheck + # DO check for available free space and abort if there isn't enough + space=quit + # Do not check for setuid files. + setuid=nocheck + # Do not check if files conflict with other packages + conflict=nocheck + # We have no action scripts. Do not check for them. + action=nocheck + # Install to the default base directory. + basedir=default + END + on host, 'pkgadd -a /root/shutupsolaris -d http://get.opencsw.org/now all' + on host, '/opt/csw/bin/pkgutil -U all' + on host, '/opt/csw/bin/pkgutil -i -y ruby19_dev' + on host, '/opt/csw/bin/pkgutil -i -y ruby19' + on host, 'ln -sf /opt/csw/bin/gem19 /usr/bin/gem' + on host, 'ln -sf /opt/csw/bin/ruby19 /usr/bin/ruby' + end + end + + install_packages_on(hosts, packages, :check_if_exists => true) + + hosts.each do |host| + case host['platform'] + when /windows/ + arch = host[:ruby_arch] || 'x86' + step "#{host} Selected architecture #{arch}" + + revision = if arch == 'x64' + '2.4.x-x64' + else + '2.4.x-x86' + end + + step "#{host} Install ruby from git using revision #{revision}" + # TODO remove this step once we are installing puppet from msi packages + win_path = on(host, 'cygpath -m /opt/puppet-git-repos').stdout.chomp + install_from_git_on(host, win_path, + :name => 'puppet-win32-ruby', + :path => build_git_url('puppet-win32-ruby'), + :rev => revision) + on host, 'cd /opt/puppet-git-repos/puppet-win32-ruby; cp -r ruby/* /' + on host, 'cd /lib; icacls ruby /grant "Everyone:(OI)(CI)(RX)"' + on host, 'cd /lib; icacls ruby /reset /T' + on host, 'cd /; icacls bin /grant "Everyone:(OI)(CI)(RX)"' + on host, 'cd /; icacls bin /reset /T' + on host, 'ruby --version' + on host, 'cmd /c gem list' + end + end + + # Only configure gem mirror after Ruby has been installed, but before any gems are installed. + configure_gem_mirror(hosts) + + hosts.each do |host| + case host['platform'] + when /solaris/ + step "#{host} Install bundler from rubygems" + on host, 'gem install bundler --no-ri --no-rdoc' + on host, "ln -sf /opt/csw/bin/bundle #{host['puppetbindir']}/bundle" + when /windows/ + on host, 'cmd /c gem install bundler --no-ri --no-rdoc' + else + on host, 'gem install bundler --no-ri --no-rdoc' + end + end + end + end + + def test_setup + test_name "Install packages and repositories on target machines..." do + require 'beaker/dsl/install_utils' + extend Beaker::DSL::InstallUtils + + source_path = Beaker::DSL::InstallUtils::SourcePath + github_sig = Beaker::DSL::InstallUtils::GitHubSig + + repositories = options[:install].map do |url| + extract_repo_info_from(build_git_url(url)) + end + + hosts.each_with_index do |host, index| + on host, "echo #{github_sig} >> $HOME/.ssh/known_hosts" + + repositories.each do |repository| + step "Install #{repository[:name]}" + if repository[:path] =~ /^file:\/\/(.+)$/ + on host, "test -d #{source_path} || mkdir -p #{source_path}" + source_dir = $1 + checkout_dir = "#{source_path}/#{repository[:name]}" + on host, "rm -f #{checkout_dir}" # just the symlink, do not rm -rf ! + on host, "ln -s #{source_dir} #{checkout_dir}" + on host, "cd #{checkout_dir} && if [ -f install.rb ]; then ruby ./install.rb ; else true; fi" + else + puppet_dir = host.tmpdir('puppet') + on(host, "chmod 755 #{puppet_dir}") + + sha = ENV['SHA'] || `git rev-parse HEAD`.chomp + gem_source = ENV["GEM_SOURCE"] || "https://rubygems.org" + gemfile_contents = <<-END + source '#{gem_source}' + gem '#{repository[:name]}', :git => '#{repository[:path]}', :ref => '#{sha}' + END + case host['platform'] + when /windows/ + create_remote_file(host, "#{puppet_dir}/Gemfile", gemfile_contents) + # bundle must be passed a Windows style path for a binstubs location + bindir = host['puppetbindir'].split(':').first + binstubs_dir = on(host, "cygpath -m \"#{bindir}\"").stdout.chomp + # note passing --shebang to bundle is not useful because Cygwin + # already finds the Ruby interpreter OK with the standard shebang of: + # !/usr/bin/env ruby + # the problem is a Cygwin style path is passed to the interpreter and this can't be modified: + # http://cygwin.1069669.n5.nabble.com/Pass-windows-style-paths-to-the-interpreter-from-the-shebang-line-td43870.html + on host, "cd #{puppet_dir} && cmd.exe /c \"bundle install --system --binstubs '#{binstubs_dir}'\"" + # puppet.bat isn't written by Bundler, but facter.bat is - copy this generic file + on host, "cd #{host['puppetbindir']} && test -f ./puppet.bat || cp ./facter.bat ./puppet.bat" + # to access gem / facter / puppet / bundle / irb with Cygwin generally requires aliases + # so that commands in /usr/bin are overridden and the binstub wrappers won't run inside Cygwin + # but rather will execute as batch files through cmd.exe + # without being overridden, Cygwin reads the shebang and causes errors like: + # C:\cygwin64\bin\ruby.exe: No such file or directory -- /usr/bin/puppet (LoadError) + # NOTE /usr/bin/puppet is a Cygwin style path that our custom Ruby build + # does not understand - it expects a standard Windows path like c:\cygwin64\bin\puppet + + # a workaround in interactive SSH is to add aliases to local session / .bashrc: + # on host, "echo \"alias puppet='C:/\\cygwin64/\\bin/\\puppet.bat'\" >> ~/.bashrc" + # note that this WILL NOT impact Beaker runs though + puppet_bundler_install_dir = on(host, "cd #{puppet_dir} && cmd.exe /c bundle show puppet").stdout.chomp + when /el-7/ + create_remote_file(host, "#{puppet_dir}/Gemfile", gemfile_contents + "gem 'json'\n") + on host, "cd #{puppet_dir} && bundle install --system --binstubs #{host['puppetbindir']}" + puppet_bundler_install_dir = on(host, "cd #{puppet_dir} && bundle show puppet").stdout.chomp + when /solaris/ + create_remote_file(host, "#{puppet_dir}/Gemfile", gemfile_contents) + on host, "cd #{puppet_dir} && bundle install --system --binstubs #{host['puppetbindir']} --shebang #{host['puppetbindir']}/ruby" + puppet_bundler_install_dir = on(host, "cd #{puppet_dir} && bundle show puppet").stdout.chomp + else + create_remote_file(host, "#{puppet_dir}/Gemfile", gemfile_contents) + on host, "cd #{puppet_dir} && bundle install --system --binstubs #{host['puppetbindir']}" + puppet_bundler_install_dir = on(host, "cd #{puppet_dir} && bundle show puppet").stdout.chomp + end + + # install.rb should also be called from the Puppet gem install dir + # this is required for the puppetres.dll event log dll on Windows + on host, "cd #{puppet_bundler_install_dir} && if [ -f install.rb ]; then ruby ./install.rb ; else true; fi" + end + end + end + + step "Hosts: create basic puppet.conf" do + hosts.each do |host| + confdir = host.puppet['confdir'] + on host, "mkdir -p #{confdir}" + puppetconf = File.join(confdir, 'puppet.conf') + + if host['roles'].include?('agent') + on host, "echo '[agent]' > '#{puppetconf}' && " + + "echo server=#{master} >> '#{puppetconf}'" + else + on host, "touch '#{puppetconf}'" + end + end + end + + step "Hosts: create environments directory like AIO does" do + hosts.each do |host| + codedir = host.puppet['codedir'] + on host, "mkdir -p #{codedir}/environments/production/manifests" + on host, "mkdir -p #{codedir}/environments/production/modules" + on host, "chmod -R 755 #{codedir}" + end + end + end + end + + def puppet_user_and_groups + test_name 'Puppet User and Group' do + hosts.each do |host| + + step "ensure puppet user and group added to all nodes because this is what the packages do" do + on host, puppet("resource user puppet ensure=present") + on host, puppet("resource group puppet ensure=present") + end + + end + end + end + + def puppet_master_sanity + test_name "Puppet Master sanity checks: PID file and SSL dir creation" do + + hostname = on(master, 'facter hostname').stdout.strip + fqdn = on(master, 'facter fqdn').stdout.strip + + with_puppet_running_on(master, :main => { :dns_alt_names => "puppet,#{hostname},#{fqdn}", :verbose => true, :noop => true }) do + # SSL dir created? + step "SSL dir created?" + on master, "[ -d #{master.puppet('master')['ssldir']} ]" + + # PID file exists? + step "PID file created?" + on master, "[ -f #{master.puppet('master')['pidfile']} ]" + end + + step "Create module directories normally handled via packaging" + on master, "mkdir -p #{master['distmoduledir']}" + on master, "mkdir -p #{master['sitemoduledir']}" + end + end + + def install_git_module(mod, hosts) + # The idea here is that each test can symlink the modules they want from a + # temporary directory to this location. This will preserve the global + # state of the system while allowing individual test cases to quickly run + # with a module "installed" in the module path. + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Clone #{mod[:url]} if needed" + on hosts, "test -d #{moddir} || mkdir -p #{moddir}" + on hosts, "test -d #{target} || git clone #{mod[:url]} #{target}" + step "Update #{mod[:name]} and check out revision #{mod[:ref]}" + + commands = ["cd #{target}", + "remote rm origin", + "remote add origin #{mod[:url]}", + "fetch origin", + "checkout -f #{mod[:ref]}", + "reset --hard refs/remotes/origin/#{mod[:ref]}", + "clean -fdx", + ] + + on hosts, commands.join(" && git ") + end + + def install_scp_module(mod, hosts) + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Purge #{target} if needed" + on hosts, "test -d #{target} && rm -rf #{target} || true" + + step "Copy #{mod[:name]} to hosts" + scp_to hosts, mod[:url].split(':', 2)[1], target + end + + # Given an array of modules specified by the --modules command line option, + # Parse all of them into an array of usable hash structures. + def list_modules(modules = []) + return [] if modules.empty? + + require 'pathname' + modules.collect do |uri| + git_url, git_ref = uri.split '#' + folder = Pathname.new(git_url).basename('.git') + name = folder.to_s.split('-', 2)[1] || folder.to_s + { + :name => name, + :url => git_url, + :folder => folder.to_s, + :ref => git_ref, + :protocol => git_url.split(':')[0].intern, + } + end + end + + def install_modules + modules = list_modules(options[:modules]) + + step "Masters: Install Puppet Modules" + masters = hosts.select { |host| host['roles'].include? 'master' } + + modules.each do |mod| + if mod[:protocol] == :scp + install_scp_module(mod, masters) + else + install_git_module(mod, masters) + end + end + end + + def install_ca_certs + test_name "Install CA Certs" do + confine :to, :platform => 'windows' + + geotrust_global_ca = <<-EOM + -----BEGIN CERTIFICATE----- + MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT + MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i + YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG + EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg + R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 + 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq + fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv + iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU + 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ + bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW + MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA + ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l + uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn + Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS + tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF + PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un + hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV + 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== + -----END CERTIFICATE----- + EOM + + usertrust_network_ca = <<-EOM + -----BEGIN CERTIFICATE----- + MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB + lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug + Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho + dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt + SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG + A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe + MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v + d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh + cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn + 0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ + M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a + MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd + oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI + DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy + oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD + VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 + dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy + bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF + BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM + //bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli + CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE + CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t + 3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS + KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== + -----END CERTIFICATE----- + EOM + + equifax_ca = <<-EOM + -----BEGIN CERTIFICATE----- + MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV + UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy + dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 + MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx + dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B + AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f + BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A + cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC + AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ + MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm + aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw + ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj + IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF + MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA + A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y + 7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh + 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 + -----END CERTIFICATE----- + EOM + + hosts.each do |host| + step "Installing Geotrust CA cert" + create_remote_file(host, "geotrustglobal.pem", geotrust_global_ca) + on host, "chmod 644 geotrustglobal.pem" + on host, "cmd /c certutil -v -addstore Root `cygpath -w geotrustglobal.pem`" + + step "Installing Usertrust Network CA cert" + create_remote_file(host, "usertrust-network.pem", usertrust_network_ca) + on host, "chmod 644 usertrust-network.pem" + on host, "cmd /c certutil -v -addstore Root `cygpath -w usertrust-network.pem`" + + step "Installing Equifax CA cert" + create_remote_file(host, "equifax.pem", equifax_ca) + on host, "chmod 644 equifax.pem" + on host, "cmd /c certutil -v -addstore Root `cygpath -w equifax.pem`" + end + end + end + end + end + end + end +end diff --git a/lib/beaker-puppet/helpers/setup/pe.rb b/lib/beaker-puppet/helpers/setup/pe.rb new file mode 100644 index 00000000..e5e38640 --- /dev/null +++ b/lib/beaker-puppet/helpers/setup/pe.rb @@ -0,0 +1,39 @@ +module Beaker + module DSL + module Helpers + module Setup + module PE + # TODO I don't think these are used anywhere + #def install + # test_name 'Install Puppet Enterprise' do + + # # This installs the latest PE build, can be overridden, see API docs + # install_pe + # end + #end + + #def update_pkg + # test_name 'Update pe-puppet pkg' do + + # repo_path = ENV['PUPPET_REPO_CONFIGS'] + # version = ENV['PUPPET_REF'] + + # unless repo_path && version + # skip_test "The puppet version to install isn't specified, using what's in the tarball..." + # end + + # hosts.each do |host| + # deploy_package_repo(host, repo_path, "pe-puppet", version) + # host.upgrade_package("pe-puppet") + # end + + # with_puppet_running_on master, {} do + # # this bounces the puppet master for us + # end + # end + #end + end + end + end + end +end diff --git a/tasks/ci.rake b/tasks/ci.rake new file mode 100644 index 00000000..25942910 --- /dev/null +++ b/tasks/ci.rake @@ -0,0 +1,246 @@ +require 'rake/clean' +require 'pp' +require 'yaml' +require 'securerandom' +require 'fileutils' +require 'beaker-hostgenerator' +require 'beaker/dsl/install_utils' +extend Beaker::DSL::InstallUtils + +REPO_CONFIGS_DIR = 'repo-configs' +CLEAN.include('*.tar', REPO_CONFIGS_DIR, 'tmp', '.beaker') + +# Default test target if none specified +DEFAULT_MASTER_TEST_TARGET = 'redhat7-64m' +DEFAULT_TEST_TARGETS = "#{DEFAULT_MASTER_TEST_TARGET}a-windows2012r2-64a" + +USAGE = <<-EOS +Usage: bundle exec rake [arguments] + +where is one of: + + ci:test:git + ci:test:aio + ci:test:gem + ci:test:quick + +See `bundle exec rake -D ` for information about each target. Each rake +task accepts the following environment variables: + +REQUIRED +-------- + +SHA: + The git SHA of either the puppet-agent package or component repo must be + specified depending on the target. The `git` and `gem` use the component + SHA, where as `aio` and `quick` use the puppet-agent package SHA. + +OPTIONAL +-------- + +HOSTS: + The hosts to run against. Must be specified as a path to a file or a + beaker-hostgenerator string. Defaults to #{DEFAULT_TEST_TARGETS}. + + HOSTS=mynodes.yaml + HOSTS=redhat7-64ma + +TESTS: + A comma-delimited list of files/directories containing tests to run. + Defaults to running all tests, except for `ci:test:quick` which only + runs a predefined set of tests. + + TESTS=tests + +OPTIONS: + Additional options to pass to beaker. Defaults to ''. + + OPTIONS='--dry-run --no-color' + +BEAKER_HOSTS: + Same as HOSTS. + +TEST_TARGET: + A beaker-hostgenerator string of agent-only hosts to run against. + The MASTER_TEST_TARGET host will added this list. This option is only + intended to be used in CI. + +MASTER_TEST_TARGET: + Override the default master test target. Should only be used with + TEST_TARGET, and is only intended to be used in CI. Defaults to + #{DEFAULT_MASTER_TEST_TARGET}. +EOS + +namespace :ci do + desc "Print usage information" + task :help do + puts USAGE + exit 1 + end + + task :check_env do + if ENV['SHA'].nil? + puts "Error: A SHA must be specified" + puts "\n" + puts USAGE + exit 1 + end + + if ENV['TESTS'].nil? + ENV['TESTS'] ||= ENV['TEST'] + ENV['TESTS'] ||= 'tests' + end + end + + task :gen_hosts do + hosts = + if ENV['HOSTS'] + ENV['HOSTS'] + elsif ENV['BEAKER_HOSTS'] + ENV['BEAKER_HOSTS'] + elsif env_config = ENV['CONFIG'] + puts 'Warning: environment variable CONFIG deprecated. Please use HOSTS to match beaker options.' + env_config + else + # By default we assume TEST_TARGET is an agent-only string + if agent_target = ENV['TEST_TARGET'] + master_target = ENV['MASTER_TEST_TARGET'] || DEFAULT_MASTER_TEST_TARGET + "#{master_target}-#{agent_target}" + else + DEFAULT_TEST_TARGETS + end + end + + if File.exists?(hosts) + ENV['HOSTS'] = hosts + else + hosts_file = "tmp/#{hosts}-#{SecureRandom.uuid}.yaml" + cli = BeakerHostGenerator::CLI.new([hosts, '--disable-default-role', '--osinfo-version', '1']) + FileUtils.mkdir_p('tmp') # -p ignores when dir already exists + File.open(hosts_file, 'w') do |fh| + fh.print(cli.execute) + end + ENV['HOSTS'] = hosts_file + end + end + + namespace :test do + desc <<-EOS +Run a limited but representative subset of acceptance tests against puppet-agent +(AIO) packages. This task is intended to reduce testing time on a per-commit +basis. + + $ SHA= bundle exec rake ci:test:quick + +SHA should be the full SHA for the puppet-agent package. +EOS + task :quick => ['ci:check_env', 'ci:gen_hosts'] do + ENV['TESTS'] = get_test_sample.join(",") + Rake::Task["ci:test:aio"].invoke + end + + desc <<-EOS +Run the acceptance tests using puppet-agent (AIO) packages. + + $ SHA= bundle exec rake ci:test:aio + +SHA should be the full SHA for the puppet-agent package. +EOS + task :aio => ['ci:check_env', 'ci:gen_hosts'] do + beaker(:init, '--hosts', ENV['HOSTS'], '--options-file', 'config/aio/options.rb') + beaker(:provision) + beaker(:exec, 'pre-suite') + beaker(:exec, ENV['TESTS']) + beaker(:destroy) + end + + desc <<-EOS +Run the acceptance tests against puppet gem on various platforms, performing a +basic smoke test. + + $ SHA= bundle exec rake:ci:gem + +SHA should be the full SHA for the component. +EOS + task :gem => ['ci:check_env'] do + beaker(:init, '--hosts', 'config/nodes/gem.yaml', '--options-file', 'config/gem/options.rb') + beaker(:provision) + beaker(:exec, 'pre-suite') + beaker(:destroy) + end + + desc <<-EOS +Run the acceptance tests against a git checkout. + + $ SHA= bundle exec rake ci:test:git + +SHA should be the full SHA for the component. Other options: + +FORK: to test against your fork, defaults to 'puppetlabs' + +SERVER: to git fetch from an alternate GIT server, defaults to 'github.com' +EOS + task :git => ['ci:check_env', 'ci:gen_hosts'] do + beaker(:init, '--hosts', ENV['HOSTS'], '--options-file', 'config/git/options.rb') + beaker(:provision) + beaker(:exec, 'pre-suite') + beaker(:exec, ENV['TESTS']) + beaker(:destroy) + end + end + + task :test_and_preserve_hosts => ['ci:check_env', 'ci:gen_hosts'] do + puts "WARNING, the test_and_preserve_hosts task is deprecated, use ci:test:aio instead." + Rake::Task['ci:test:aio'].execute + end +end + +task :default do + sh('rake -T') +end + +task :spec do + sh('rspec lib') +end + +def beaker(command, *argv) + argv.concat(ENV['OPTIONS'].split(' ')) if ENV['OPTIONS'] + + sh('beaker', command.to_s, *argv) +end + +def get_test_sample + # This set represents a reasonable sample of puppet acceptance tests, + # covering a wide range of features and code susceptible to regressions. + tests = [ 'tests/direct_puppet/cached_catalog_remediate_local_drift.rb', + 'tests/resource/file/content_attribute.rb', + 'tests/puppet_apply_basics.rb', + 'tests/modules/install/basic_install.rb', + 'tests/face/loadable_from_modules.rb', + 'tests/language/functions_in_puppet_language.rb', + 'tests/node/check_woy_cache_works.rb', + 'tests/parser_functions/calling_all_functions.rb', + 'tests/ticket_4622_filebucket_diff_test.rb', + 'tests/pluginsync/4420_pluginfacts_should_be_resolvable_on_agent.rb', + 'tests/ssl/puppet_cert_generate_and_autosign.rb', + 'tests/resource/package/yum.rb', + 'tests/resource/service/ticket_5024_systemd_enabling_masked_service.rb', + 'tests/resource/service/puppet_mcollective_service_management.rb' + ] + + # Add any tests modified within the last two weeks to the list, excluding + # deleted ones. We can't rely on --diff-filter, because an acceptance + # test may be modified and then deleted in the same time range. + modified = `git log --name-only --pretty="format:" --since 2.weeks ./tests` + tests += modified.split("\n").reject do |s| + s.empty? + end.collect do |s| + s.sub('acceptance/', '') + end.select do |s| + s =~ /\.rb$/ + end.find_all do |s| + File.exist?(s) + end + + tests.uniq.sort +end