├── .gitignore ├── .gitmodules ├── .travis.yml ├── Boltdir └── site-modules │ └── pxp_dev │ ├── lib │ └── puppet │ │ └── functions │ │ ├── pxp_root.rb │ │ └── replace_inventory.rb │ ├── plans │ ├── build.pp │ ├── rebuild.pp │ └── upload_to_agent.pp │ └── tasks │ ├── build_deps_with_vanagon.json │ ├── build_deps_with_vanagon.rb │ ├── run_pxp_make.json │ ├── run_pxp_make.sh │ ├── upload_from_builder_to_agent.sh │ └── upload_from_builder_to_agents.json ├── CHANGELOG.md ├── CMakeLists.txt ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── acceptance ├── .beaker.yml ├── .gitignore ├── Gemfile ├── README.md ├── Rakefile ├── config │ └── aio │ │ └── options.rb ├── files │ ├── complex-args.json │ ├── complex-output │ ├── complex-task.ps1 │ ├── environments │ │ └── bolt │ │ │ ├── init │ │ │ ├── init.bat │ │ │ ├── testing_file.txt │ │ │ ├── unix_script.sh │ │ │ └── win_script.ps1 │ └── modules │ │ └── basic │ │ ├── facts.d │ │ └── another.json │ │ ├── files │ │ └── data │ │ ├── lib │ │ ├── puppet │ │ │ ├── feature │ │ │ │ └── can_warn.rb │ │ │ ├── functions │ │ │ │ └── pid.rb │ │ │ ├── provider │ │ │ │ └── warn │ │ │ │ │ └── basic.rb │ │ │ └── type │ │ │ │ └── warn.rb │ │ └── puppet_x │ │ │ └── util │ │ │ └── warn.rb │ │ ├── manifests │ │ ├── init.pp │ │ ├── strict.pp │ │ └── strict_variables.pp │ │ └── metadata.json ├── lib │ ├── helper.rb │ ├── puppet │ │ └── acceptance │ │ │ └── environment_utils.rb │ └── pxp-agent │ │ ├── bolt_pxp_module_helper.rb │ │ ├── config_helper.rb │ │ └── test_helper.rb ├── setup │ └── common │ │ ├── 045_SetPuppetServerOnAgents.rb │ │ ├── 050_Setup_Broker.rb │ │ ├── 060_Setup_PCP_Client.rb │ │ └── 070_Setup_Server_fixtures.rb ├── teardown │ └── common │ │ └── 099_Archive_Logs.rb └── tests │ ├── crl_revocation.rb │ ├── failover │ ├── intentional_shutdown_failover.rb │ └── timeout_failover.rb │ ├── invalid_ssl_config.rb │ ├── multiple_pxp_agent_daemon.rb │ ├── proxy_connection.rb │ ├── pxp_agent_associate.rb │ ├── pxp_module_bolt_apply │ └── apply_catalog.rb │ ├── pxp_module_bolt_command │ └── run_command.rb │ ├── pxp_module_bolt_download_file │ └── download_file.rb │ ├── pxp_module_bolt_script │ └── run_script.rb │ ├── pxp_module_bolt_task │ ├── run_echo.rb │ ├── run_multifile.rb │ ├── run_ps1.rb │ ├── run_puppet.rb │ ├── run_ruby.rb │ └── task_download.rb │ ├── pxp_module_puppet │ ├── negative │ │ └── attempt_puppet_flag_whitelist_bypass.rb │ ├── rerun_transaction.rb │ ├── restart_pxp_agent_during_non_blocking_run.rb │ ├── run_puppet.rb │ ├── run_puppet_agent_disabled.rb │ ├── run_puppet_agent_non_root.rb │ ├── run_puppet_during_puppet.rb │ ├── run_puppet_exec.rb │ ├── run_puppet_killed_puppet.rb │ ├── run_puppet_nonASCII.rb │ ├── run_puppet_result_changed.rb │ ├── run_puppet_result_failed.rb │ ├── run_puppet_twice.rb │ ├── run_puppet_umask.rb │ └── run_puppet_v1.rb │ ├── reconnect_after_broker_unavailable.rb │ ├── service_stop_start.rb │ └── validate_file_paths.rb ├── appveyor.yml ├── cmake ├── FindCPPHOCON.cmake └── Findcpp-pcp-client.cmake ├── exe ├── CMakeLists.txt ├── PowershellShim-Helper.ps1 ├── PowershellShim.ps1 ├── README.md ├── apply_ruby_shim.rb ├── execution_wrapper.cc ├── main.cc └── tests │ ├── Echo.ps1 │ ├── PowershellShim.Tests.ps1 │ └── main.cc ├── ext ├── debian │ ├── pxp-agent.default │ └── pxp-agent.init ├── osx │ ├── pxp-agent.newsyslog.conf │ └── pxp-agent.plist ├── pxp-agent.logrotate ├── redhat │ ├── pxp-agent.init │ └── pxp-agent.sysconfig ├── solaris │ └── smf │ │ └── pxp-agent.xml ├── suse │ └── pxp-agent.init └── systemd │ ├── pxp-agent.logrotate │ └── pxp-agent.service ├── lib ├── CMakeLists.txt ├── inc │ └── pxp-agent │ │ ├── action_output.hpp │ │ ├── action_request.hpp │ │ ├── action_response.hpp │ │ ├── action_status.hpp │ │ ├── agent.hpp │ │ ├── configuration.hpp │ │ ├── external_module.hpp │ │ ├── module.hpp │ │ ├── module_cache_dir.hpp │ │ ├── module_type.hpp │ │ ├── modules │ │ ├── apply.hpp │ │ ├── command.hpp │ │ ├── echo.hpp │ │ ├── file.hpp │ │ ├── ping.hpp │ │ ├── script.hpp │ │ └── task.hpp │ │ ├── pxp_connector.hpp │ │ ├── pxp_connector_v1.hpp │ │ ├── pxp_connector_v2.hpp │ │ ├── pxp_schemas.hpp │ │ ├── request_processor.hpp │ │ ├── request_type.hpp │ │ ├── results_mutex.hpp │ │ ├── results_storage.hpp │ │ ├── thread_container.hpp │ │ ├── time.hpp │ │ └── util │ │ ├── bolt_helpers.hpp │ │ ├── bolt_module.hpp │ │ ├── daemonize.hpp │ │ ├── posix │ │ └── pid_file.hpp │ │ ├── process.hpp │ │ ├── purgeable.hpp │ │ └── utf8.hpp ├── src │ ├── action_request.cc │ ├── action_response.cc │ ├── agent.cc │ ├── configuration.cc │ ├── configuration │ │ ├── posix │ │ │ └── configuration.cc │ │ └── windows │ │ │ └── configuration.cc │ ├── external_module.cc │ ├── module.cc │ ├── module_cache_dir.cc │ ├── modules │ │ ├── apply.cc │ │ ├── command.cc │ │ ├── echo.cc │ │ ├── file.cc │ │ ├── ping.cc │ │ ├── script.cc │ │ └── task.cc │ ├── pxp_connector_v1.cc │ ├── pxp_connector_v2.cc │ ├── pxp_schemas.cc │ ├── request_processor.cc │ ├── results_mutex.cc │ ├── results_storage.cc │ ├── thread_container.cc │ ├── time.cc │ └── util │ │ ├── bolt_helpers.cc │ │ ├── bolt_module.cc │ │ ├── posix │ │ ├── daemonize.cc │ │ ├── pid_file.cc │ │ └── process.cc │ │ ├── utf8.cc │ │ └── windows │ │ ├── daemonize.cc │ │ └── process.cc └── tests │ ├── CMakeLists.txt │ ├── common │ ├── certs.cc │ ├── certs.hpp │ ├── content_format.hpp │ ├── mock_connector.cc │ └── mock_connector.hpp │ ├── component │ └── external_modules_interface_test.cc │ ├── main.cc │ ├── resources │ ├── action_results │ │ ├── broken │ │ │ ├── exitcode │ │ │ ├── metadata │ │ │ ├── pid │ │ │ ├── stderr │ │ │ └── stdout │ │ └── valid │ │ │ ├── exitcode │ │ │ ├── metadata │ │ │ ├── pid │ │ │ ├── stderr │ │ │ └── stdout │ ├── broken_modules │ │ ├── reverse_bad_json_format │ │ ├── reverse_bad_json_format.bat │ │ ├── reverse_no_metadata │ │ └── reverse_no_metadata.bat │ ├── config │ │ ├── bad-broker.conf │ │ ├── bad-master.conf │ │ ├── ca_crt.pem │ │ ├── duplicate.conf │ │ ├── empty-pxp-agent.conf │ │ ├── foo.cfg │ │ ├── multi-broker-both-uris.conf │ │ ├── multi-broker-master-uris.conf │ │ ├── multi-broker.conf │ │ ├── test_crl.pem │ │ ├── test_crt.pem │ │ ├── test_key.pem │ │ └── unknown.conf │ ├── download_files │ │ └── file.txt │ ├── modules │ │ ├── check_output.rb │ │ ├── convert_test │ │ ├── convert_test.bat │ │ ├── failures_test │ │ ├── failures_test.bat │ │ ├── reverse_valid │ │ └── reverse_valid.bat │ ├── modules_broken │ │ ├── reverse_broken │ │ └── reverse_broken.bat │ ├── modules_config_bad_format │ │ └── reverse_valid.conf │ ├── modules_config_broken │ │ └── reverse_valid.conf │ ├── modules_config_valid │ │ └── reverse_valid.conf │ ├── purge_test │ │ ├── valid_old │ │ │ └── metadata │ │ └── valid_recent │ │ │ └── metadata │ ├── scripts_cache │ │ ├── 3D57FCC5FBDC53E667D16493D38621F36B2CF7DB244986B07A4FBCFECE9E19BD │ │ │ ├── test script.ps1 │ │ │ └── test_script.ps1 │ │ └── 71A2725F36221AAE75AF076D01D18744DA461A02DD2AB746447BC41C71248562 │ │ │ └── test_script.sh │ └── tasks-cache │ │ ├── 0d75633b5dd4b153496b4593e9d94e69265d2a812579f724ba0b4422b0bfb836 │ │ └── unparseable.bat │ │ ├── 15f26bdeea9186293d256db95fed616a7b823de947f4e9bd0d8d23c5ac786d13 │ │ └── init │ │ ├── 1c616ed98f54880444d0c49036cdf930120457c20e7a9a204db750f2d6162999 │ │ └── printer.bat │ │ ├── 28dd63928f9e3837e11e36f5af35c09068e4d62b355cf169a873a0cdf30f7c95 │ │ └── init.sh │ │ ├── 554f86a33add88c371c2bbb79839c9adfd3d420dc5f405a07e97fab54efbe1ba │ │ └── error.bat │ │ ├── 67ee5478eaadb034ba59944eb977797b49ca6aa8d3574587f36ebcbeeb65f70e │ │ └── file2.txt │ │ ├── 823c013467ce03b12dbe005757a6c842894373e8bcfb0cf879329afb5abcd543 │ │ └── multi │ │ ├── 88a07e5b672aa44a91aa7d63e22c91510af5d4707e12f75e0d5de2dfdbde1dec │ │ └── multi.bat │ │ ├── 936e85a9b7f1e7b4b593c9f051a36105ed36f7fb8dcff67ff23a3a9af2abe962 │ │ └── printer │ │ ├── 94f6e58bd04a4513b8301e75f40527cf7610c66d1960b26f6ac2e743e108bdac │ │ └── file3.txt │ │ ├── b26e34bc50c88ca5ee2bfcbcaff5c23b0124db9479e66390539f2715b675b7e7 │ │ └── null_byte │ │ ├── d2795e0a1b66ca75be9e2be25c2a61fdbab9efc641f8e480f5ab1b348112701d │ │ └── unparseable │ │ ├── d5b8819b51ecd53b32de74c09def0e71f617076bc8e4f75e1eac99b8f77a6c70 │ │ └── error │ │ ├── e1c10f8c709f06f4327ac6a07a918e297a039a24a788fabf4e2ebc31d16e8dc3 │ │ └── init.bat │ │ ├── ea7d652bfe797121a7ac8e3654aacf7d50a9a4665e843669b873995b072d820b │ │ └── init.bat │ │ └── ecdc5536f73bdae8816f0ea40726ef5e9b810d914493075903bb90623d97b1d8 │ │ └── file1.txt │ └── unit │ ├── action_request_test.cc │ ├── action_response_test.cc │ ├── agent_test.cc │ ├── configuration_test.cc │ ├── external_module_test.cc │ ├── module_cache_dir_test.cc │ ├── module_test.cc │ ├── modules │ ├── apply_test.cc │ ├── command_test.cc │ ├── file_test.cc │ ├── ping_test.cc │ ├── script_test.cc │ └── task_test.cc │ ├── pxp_connector_v1_test.cc │ ├── pxp_connector_v2_test.cc │ ├── request_processor_test.cc │ ├── results_mutex_test.cc │ ├── results_storage_test.cc │ ├── thread_container_test.cc │ ├── time_test.cc │ └── util │ ├── posix │ └── pid_file_test.cc │ └── process_test.cc ├── locales └── pxp-agent.pot ├── modules ├── .gitignore ├── Gemfile ├── README.md ├── pxp-module-puppet ├── pxp-module-puppet.bat ├── pxp-module-puppet.md └── spec │ ├── fixtures │ └── last_run_report.yaml │ └── unit │ └── modules │ └── puppet_spec.rb ├── templates ├── root_path.hpp └── version-inl.hpp └── vendor ├── FindDependency.cmake ├── horsewhisperer-0.13.1.zip └── horsewhisperer.cmake /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /build/ 3 | /dev-resources/ 4 | /lib/tests/resources/test_spool/ 5 | /lib/tests/resources/cache_dir/ 6 | /lib/tests/resources/scripts_cache/FAILINGSHA256 7 | /lib/tests/resources/scripts_cache/SCRIPT_FAILING_SHA 8 | /lib/tests/resources/tmp/ 9 | /lib/tests/resources/cache_dir/ 10 | .idea/ 11 | .DS_Store 12 | /acceptance/junit 13 | /acceptance/.beaker 14 | /acceptance/sut-files.tgz 15 | /Boltdir/inventory.yaml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pxp-agent/ff0f0b6b9930b14d00e93dd3cdc6720dc025bc53/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | 5 | before_install: 6 | - docker pull gcr.io/cpp-projects/cpp-ci:1 7 | 8 | script: 9 | - > 10 | docker run -v `pwd`:/pxp-agent gcr.io/cpp-projects/cpp-ci:3 /bin/bash -c " 11 | wget https://github.com/puppetlabs/leatherman/releases/download/${LEATHERMAN_VERSION}/leatherman.tar.gz && 12 | tar xzvf leatherman.tar.gz --strip 1 -C / && 13 | wget https://github.com/puppetlabs/cpp-pcp-client/releases/download/${CPP_PCP_CLIENT_VERSION}/cpp-pcp-client.tar.gz && 14 | tar xzvf cpp-pcp-client.tar.gz --strip 1 -C / && 15 | wget https://github.com/puppetlabs/cpp-hocon/releases/download/${CPP_HOCON_VERSION}/cpp-hocon.tar.gz && 16 | tar xzvf cpp-hocon.tar.gz --strip 1 -C / && 17 | cd /pxp-agent && 18 | cmake $EXTRA_VARS . && 19 | mkdir dest && 20 | make $TARGET DESTDIR=/pxp-agent/dest VERBOSE=1 -j2 && 21 | { [[ '$COVERALLS' != 'ON' ]] || coveralls --gcov-options '\-lp' -r . -b . -e src -e vendor >/dev/null || true; } 22 | " 23 | 24 | env: 25 | global: 26 | - LEATHERMAN_VERSION=1.11.0 27 | - CPP_PCP_CLIENT_VERSION=1.7.6 28 | - CPP_HOCON_VERSION=0.2.0 29 | matrix: 30 | - TARGET=cpplint 31 | - TARGET=cppcheck 32 | - TARGET="all test install ARGS=-V" EXTRA_VARS="-DBOOST_STATIC=ON" 33 | - TARGET="all test install ARGS=-V" EXTRA_VARS="-DBOOST_STATIC=ON -DCMAKE_BUILD_TYPE=Debug -DCOVERALLS=ON" COVERALLS=ON 34 | 35 | notifications: 36 | email: false 37 | -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/lib/puppet/functions/pxp_root.rb: -------------------------------------------------------------------------------- 1 | Puppet::Functions.create_function(:'pxp_root') do 2 | def pxp_root 3 | File.absolute_path(File.join(__dir__, "..", "..", "..", "..", "..", "..")) 4 | end 5 | end -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/lib/puppet/functions/replace_inventory.rb: -------------------------------------------------------------------------------- 1 | Puppet::Functions.create_function(:'replace_inventory') do 2 | def replace_inventory(target_hash, path_to_inventory) 3 | contents = { 'version' => 2, 4 | 'targets' => [target_hash] }.to_yaml 5 | if File.exists?(path_to_inventory) 6 | File.delete(path_to_inventory) 7 | end 8 | File.open(path_to_inventory,"w") do |f| 9 | f.write(contents) 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/plans/build.pp: -------------------------------------------------------------------------------- 1 | plan pxp_dev::build ( 2 | TargetSpec $target_agents, 3 | String $agent_ref, 4 | String $target_host_type, 5 | Optional[String] $winrm_pass = undef, 6 | Optional[Boolean] $no_bundler = false, 7 | ) { 8 | $pxp_root = pxp_root() 9 | $vanagon_builder_hostname = run_task('pxp_dev::build_deps_with_vanagon', localhost, agent_ref => $agent_ref, os_type => $target_host_type, no_bundler => $no_bundler).first().value()['build_host'] 10 | if $target_host_type =~ /^win/ { 11 | $build_host_opts_hash = {'uri' => $vanagon_builder_hostname, 12 | 'name' => 'build_host', 13 | 'config' => { 14 | 'transport' => 'winrm', 15 | 'winrm' => { 16 | 'user' => 'Administrator', 17 | 'password' => $winrm_pass, 18 | 'ssh' => false, 19 | } 20 | } 21 | } 22 | } else { 23 | $build_host_opts_hash = {'uri' => $vanagon_builder_hostname, 24 | 'name' => 'build_host', 25 | 'config' => { 26 | 'transport' => 'ssh', 27 | 'ssh' => { 28 | 'host-key-check' => false 29 | } 30 | } 31 | } 32 | } 33 | $build_host = Target.new($build_host_opts_hash) 34 | replace_inventory($build_host_opts_hash, file::join($pxp_root, 'Boltdir', 'inventory.yaml')) 35 | 36 | $build_location = strip(run_command('dirname $(find /var/tmp/ -maxdepth 2 -name cpp-pcp-client)', $build_host).first().value()['stdout']) 37 | upload_file($pxp_root, file::join($build_location, 'pxp-agent'), $build_host) 38 | run_task('pxp_dev::run_pxp_make', $build_host, run_cmake => true) 39 | run_plan('pxp_dev::upload_to_agent',target_agents => $target_agents, target_host_type => $target_agents, winrm_pass => $winrm_pass) 40 | } 41 | -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/plans/rebuild.pp: -------------------------------------------------------------------------------- 1 | plan pxp_dev::rebuild ( 2 | TargetSpec $target_agents, 3 | String $target_host_type, 4 | Optional[Boolean] $run_cmake = false, 5 | Optional[String] $winrm_pass = undef, 6 | ) { 7 | $pxp_root = pxp_root() 8 | $build_host = get_target('build_host') 9 | $build_location = strip(run_command('find /var/tmp/ -maxdepth 2 -name pxp-agent', $build_host).first().value()['stdout']) 10 | if $target_host_type =~ /^win/ { 11 | upload_file($pxp_root, file::join($build_location, 'pxp-agent'), $build_host) 12 | } else { 13 | $build_hostname = $build_host.host() 14 | run_command("rsync ${pxp_root} ${build_hostname}:${build_location}", localhost) 15 | } 16 | run_task('pxp_dev::run_pxp_make', $build_host, run_cmake => $run_cmake) 17 | run_plan('pxp_dev::upload_to_agent', target_agents => $target_agents, target_host_type => $target_agents, winrm_pass => $winrm_pass) 18 | } 19 | -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/plans/upload_to_agent.pp: -------------------------------------------------------------------------------- 1 | plan pxp_dev::upload_to_agent ( 2 | TargetSpec $target_agents, 3 | String $target_host_type, 4 | Optional[String] $winrm_pass = undef, 5 | ) { 6 | $status = run_task('service', $target_agents, action => 'status', name => 'pxp-agent') 7 | $running_agents = $status.filter |$status_result| { 8 | $status_result.value()['enabled'] == 'enabled' 9 | } 10 | # Stop any running pxp-agents before replacing the executable 11 | run_task('service', $running_agents, action => 'stop', name => 'pxp-agent') 12 | 13 | $build_host = get_target('build_host') 14 | run_task('pxp_dev::upload_from_builder_to_agent', $build_host, agents => $target_agents) 15 | 16 | # restart any previously stopped agents 17 | run_task('service', $running_agents, action => 'start', name => 'pxp-agent') 18 | } 19 | -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/build_deps_with_vanagon.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "description": "Build pxp-agent on a new pooler VM", 4 | "parameters": { 5 | "agent_ref": { 6 | "description": "Git reference for the puppet-agent repo that will build the pxp-agent, can be a branch, SHA, or tag", 7 | "type": "String" 8 | }, 9 | "os_type": { 10 | "description": "The type of operating system to build for, i.e. redhat-7-x86_64 or ubuntu-18.04-amd64", 11 | "type": "String" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/build_deps_with_vanagon.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require 'tmpdir' 5 | require 'fileutils' 6 | 7 | params = JSON.parse(STDIN.read) 8 | 9 | workdir = Dir.mktmpdir 10 | build_host = '' 11 | Dir.chdir(workdir) do 12 | `git clone git@github.com:puppetlabs/puppet-agent.git 1>&2` 13 | Dir.chdir('puppet-agent') do 14 | `git checkout #{params['agent_ref']} 1>&2` 15 | if params['no_bundler'] 16 | `build puppet-agent #{params['os_type']} --preserve --only_build=cpp-pcp-client,cpp-hocon 1>&2` 17 | else 18 | `bundle install 1>&2` 19 | `bundle exec build puppet-agent #{params['os_type']} --preserve --only_build=cpp-pcp-client,cpp-hocon 1>&2` 20 | end 21 | build_host = File.read('vanagon_hosts.log').match(/Reserving\s[A-Za-z\-\.]*\s/).to_s.gsub('Reserving', '').strip 22 | end 23 | end 24 | 25 | $stdout.puts JSON.generate({'build_host' => build_host}) -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/run_pxp_make.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "description": "re-build pxp-agent on a an existing build host", 4 | "parameters": { 5 | "run_cmake": { 6 | "description": "set to true to run the cmake command before make", 7 | "type": "Boolean" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/run_pxp_make.sh: -------------------------------------------------------------------------------- 1 | #/usr/bin/env sh 2 | 3 | pushd $(find /var/tmp/ -maxdepth 2 -name pxp-agent) >/dev/null 1>&2 4 | if [[ $PT_run_cmake == "true" ]]; then 5 | export PATH="/opt/puppetlabs/puppet/bin:/opt/pl-build-tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin" && \ 6 | /opt/pl-build-tools/bin/cmake -DCMAKE_TOOLCHAIN_FILE=/opt/pl-build-tools/pl-build-toolchain.cmake \ 7 | -DLEATHERMAN_GETTEXT=ON \ 8 | -DCMAKE_VERBOSE_MAKEFILE=ON \ 9 | -DCMAKE_PREFIX_PATH=/opt/puppetlabs/puppet \ 10 | -DCMAKE_INSTALL_RPATH=/opt/puppetlabs/puppet/lib \ 11 | -DCMAKE_SYSTEM_PREFIX_PATH=/opt/puppetlabs/puppet \ 12 | -DMODULES_INSTALL_PATH=/opt/puppetlabs/pxp-agent/modules \ 13 | -DCMAKE_INSTALL_PREFIX=/opt/puppetlabs/puppet 14 | fi 15 | make -j8 install 16 | popd >/dev/null 1>&2 17 | -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/upload_from_builder_to_agent.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | for AGENT in $PT_agents 4 | do 5 | scp -o StrictHostKeyChecking=no /opt/puppetlabs/puppet/bin/pxp-agent "$AGENT:/opt/puppetlabs/puppet/bin/pxp-agent" 6 | scp -o StrictHostKeyChecking=no -r /opt/puppetlabs/pxp-agent "$AGENT:/opt/puppetlabs/pxp-agent" 7 | done -------------------------------------------------------------------------------- /Boltdir/site-modules/pxp_dev/tasks/upload_from_builder_to_agents.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "description": "Build pxp-agent on a new pooler VM", 4 | "parameters": { 5 | "agents": { 6 | "description": "An array of agents to upload the new pxp-agent to", 7 | "type": "Array[String]" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # default to skeletor 2 | * @puppetlabs/skeletor 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Third-party patches are essential for keeping Puppet open-source projects 4 | great. We want to keep it as easy as possible to contribute changes that 5 | allow you to get the most out of our projects. There are a few guidelines 6 | that we need contributors to follow so that we can have a chance of keeping on 7 | top of things. For more info, see our canonical guide to contributing: 8 | 9 | [https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md) 10 | -------------------------------------------------------------------------------- /acceptance/.beaker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh: 3 | keys: 4 | - id_rsa_acceptance 5 | - ~/.ssh/id_rsa-acceptance 6 | xml: true 7 | timesync: false 8 | repo_proxy: true 9 | add_el_extras: false 10 | forge_host: api-forge-aio02-petest.puppet.com 11 | 'master-start-curl-retries': 30 12 | log_level: debug 13 | preserve_hosts: onfail 14 | helper: ./lib/helper.rb 15 | options_file: ./config/aio/options.rb 16 | -------------------------------------------------------------------------------- /acceptance/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | tmp 3 | empty.rb 4 | .bundle 5 | Gemfile.local 6 | Gemfile.lock 7 | log/ 8 | merged_options.rb 9 | hosts.yaml 10 | -------------------------------------------------------------------------------- /acceptance/Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 2 | 3 | def location_for(place, fake_version = nil) 4 | if place.is_a?(String) && place =~ /^((?:git[:@]|https:)[^#]*)#(.*)/ 5 | [fake_version, { :git => $1, :branch => $2, :require => false }].compact 6 | elsif place.is_a?(String) && place =~ /^file:\/\/(.*)/ 7 | ['>= 0', { :path => File.expand_path($1), :require => false }] 8 | else 9 | [place, { :require => false }] 10 | end 11 | end 12 | 13 | gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '~> 6') 14 | gem 'beaker-puppet', *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 4') 15 | gem "beaker-hostgenerator", *location_for(ENV['BEAKER_HOSTGENERATOR_VERSION'] || "~> 2.0") 16 | gem "beaker-abs", *location_for(ENV['BEAKER_ABS_VERSION'] || "~> 1") 17 | gem "beaker-vagrant", *location_for(ENV['BEAKER_VAGRANT_VERSION'] || "~> 0") 18 | gem "beaker-vmpooler", *location_for(ENV['BEAKER_VMPOOLER_VERSION'] || "~> 1") 19 | gem "beaker-gke", *location_for(ENV['BEAKER_GKE_VERSION'] || "~> 0.0.3") 20 | gem 'rake' 21 | gem 'scooter', '~> 4.0' 22 | gem 'pcp-client', '~> 0.5.2' 23 | 24 | group(:test) do 25 | gem 'rspec', '~> 2.14.0', :require => false 26 | gem 'mocha', '~> 0.10.5', :require => false 27 | end 28 | 29 | if File.exist? "#{__FILE__}.local" 30 | eval(File.read("#{__FILE__}.local"), binding) 31 | end 32 | -------------------------------------------------------------------------------- /acceptance/README.md: -------------------------------------------------------------------------------- 1 | # Acceptance Tests 2 | 3 | Acceptance tests use https://github.com/puppetlabs/pcp-broker#HEAD by default. 4 | 5 | They can be configured to test against other versions of pcp-broker by setting environment variables 6 | 7 | PCP_BROKER_FORK=puppetlabs 8 | PCP_BROKER_REF=0.8.4 9 | 10 | Default settings use PCP v2. To test using PCP v1 set 11 | 12 | PCP_VERSION=1 13 | 14 | To run acceptance tests, a build of puppet-agent is required. An example of how to run them is 15 | 16 | rake ci:test:aio SHA=1.8.2 TEST_TARGET=centos7-64a 17 | 18 | You can run `rake ci:test:aio` to get more detailed help about how to configure testing. 19 | 20 | Additionally, PACKAGE can be set to install a local package built via [puppet-agent](https://github.com/puppetlabs/puppet-agent) 21 | on agents during testing; SHA is still required for the version to install on the master. For example 22 | 23 | rake ci:test:aio SHA=1.8.2 TEST_TARGET=centos7-64a PACKAGE=/path/to/puppet-agent/output/el/7/PC1/x86_64/puppet-agent-5.0.1.190.g4263fa6d-1.el7.x86_64.rpm 24 | -------------------------------------------------------------------------------- /acceptance/Rakefile: -------------------------------------------------------------------------------- 1 | require 'beaker-puppet' 2 | Beaker::DSL::Helpers::RakeHelpers.load_tasks 3 | -------------------------------------------------------------------------------- /acceptance/config/aio/options.rb: -------------------------------------------------------------------------------- 1 | { 2 | :type => 'aio', 3 | :is_puppetserver => true, 4 | :'use-service' => true, # use service scripts for start/stop 5 | :puppetservice => 'puppetserver', 6 | :'puppetserver-confdir' => '/etc/puppetlabs/puppetserver/conf.d', 7 | :pre_suite => [ 8 | "setup/common/045_SetPuppetServerOnAgents.rb", 9 | "setup/common/050_Setup_Broker.rb", 10 | "setup/common/060_Setup_PCP_Client.rb", 11 | "setup/common/070_Setup_Server_fixtures.rb", 12 | ], 13 | :post_suite => [ 14 | 'teardown/common/099_Archive_Logs.rb', 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /acceptance/files/complex-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "argstring": "hello all", 3 | "argint32": 5, 4 | "argfloat": 42.42, 5 | "arghashtable": { 6 | "key": "value", 7 | "key2": 2, 8 | "key3": true 9 | }, 10 | "argarray": [0, "foo", true, { "key": "value" }], 11 | "argarrayofhashes": [ 12 | { 13 | "name": "puppet", 14 | "version_requirement": ">= 3.7.0 < 6.0.0" 15 | }, 16 | { 17 | "name": "pe", 18 | "version_requirement": ">= 5.0.0" 19 | } 20 | ], 21 | "argfileinfo": "/Users/foo/source/puppet/Gemfile", 22 | "argbool": false, 23 | "argtimespan": "12:34:56", 24 | "argguid": "74be6039-e777-45fe-aa56-3d356443e096", 25 | "argregex": "^http:\\/\\/", 26 | "argswitch": true, 27 | "types": "foo" 28 | } 29 | -------------------------------------------------------------------------------- /acceptance/files/complex-output: -------------------------------------------------------------------------------- 1 | Defined with arguments: 2 | * ArgArray of type array 3 | * ArgArrayOfHashes of type hashtable[] 4 | * ArgBool of type bool 5 | * ArgFileInfo of type System.IO.FileInfo 6 | * ArgFloat of type float 7 | * ArgGuid of type guid 8 | * ArgHashtable of type hashtable 9 | * ArgInt32 of type int 10 | * ArgRegex of type regex 11 | * ArgString of type string 12 | * ArgSwitch of type switch 13 | * ArgTimeSpan of type timespan 14 | 15 | Received arguments: 16 | * ArgArray (System.Object[]): 17 | 0 18 | foo 19 | True 20 | key: value 21 | * ArgArrayOfHashes (hashtable[]): 22 | name: puppet 23 | version_requirement: >= 3.7.0 < 6.0.0 24 | name: pe 25 | version_requirement: >= 5.0.0 26 | * ArgBool (bool): 27 | False 28 | * ArgFileInfo (System.IO.FileInfo): 29 | /Users/foo/source/puppet/Gemfile 30 | * ArgFloat (float): 31 | 42.42 32 | * ArgGuid (guid): 33 | 74be6039-e777-45fe-aa56-3d356443e096 34 | * ArgHashtable (hashtable): 35 | key: value 36 | key2: 2 37 | key3: True 38 | * ArgInt32 (int): 39 | 5 40 | * ArgRegex (regex): 41 | ^http:\/\/ 42 | * ArgString (string): 43 | hello all 44 | * ArgSwitch (switch): 45 | True 46 | * ArgTimeSpan (timespan): 47 | 12:34:56 48 | * types (string): 49 | foo 50 | -------------------------------------------------------------------------------- /acceptance/files/complex-task.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [Parameter(Mandatory = $True)] 4 | [string] 5 | $ArgString, 6 | 7 | [Parameter(Mandatory = $False)] 8 | [Int32] # aka [Int] 9 | $ArgInt32, 10 | 11 | [Parameter(Mandatory = $False)] 12 | [Float] # aka [Single] 13 | $ArgFloat, 14 | 15 | [Parameter(Mandatory = $False)] 16 | [Hashtable] 17 | $ArgHashtable, 18 | 19 | [Parameter(Mandatory = $False)] 20 | [Array] 21 | $ArgArray, 22 | 23 | [Parameter(Mandatory = $False)] 24 | [Hashtable[]] # any array of type[] should work 25 | $ArgArrayOfHashes, 26 | 27 | [Parameter(Mandatory = $False)] 28 | [IO.FileInfo] # aka a file path 29 | $ArgFileInfo, 30 | 31 | [Parameter(Mandatory = $False)] 32 | [Bool] 33 | $ArgBool, 34 | 35 | [Parameter(Mandatory = $False)] 36 | [TimeSpan] 37 | $ArgTimeSpan, 38 | 39 | [Parameter(Mandatory = $False)] 40 | [Guid] 41 | $ArgGuid, 42 | 43 | [Parameter(Mandatory = $False)] 44 | [Regex] 45 | $ArgRegex, 46 | 47 | [Parameter(Mandatory = $False)] 48 | [Switch] 49 | $ArgSwitch, 50 | 51 | # Parameters may have the string `type` in them, but not have the name `type` 52 | [Parameter(Mandatory = $False)] 53 | [String] 54 | $types 55 | ) 56 | 57 | function ConvertTo-String 58 | { 59 | [CmdletBinding()] 60 | param( 61 | [Parameter(Mandatory = $True, ValueFromPipeline = $True)] 62 | $Object 63 | ) 64 | 65 | begin 66 | { 67 | $outputs = @() 68 | } 69 | process 70 | { 71 | if ($Object -is [Hashtable]) 72 | { 73 | $outputs += (HashtableTo-String $Object) 74 | } 75 | else 76 | { 77 | $outputs += $Object 78 | } 79 | } 80 | end 81 | { 82 | $outputs -join "`n" 83 | } 84 | } 85 | 86 | function HashtableTo-String 87 | { 88 | [CmdletBinding()] 89 | param( 90 | [Parameter(Mandatory = $True, ValueFromPipeline = $True)] 91 | [Hashtable] 92 | $Hashtable 93 | ) 94 | 95 | ($Hashtable.GetEnumerator() | Sort-Object -Property Key | % { "$($_.Key): $($_.Value)" }) -join "`n" 96 | } 97 | 98 | Write-Output "Defined with arguments:" 99 | 100 | $PSCmdlet.MyInvocation.MyCommand.Parameters.GetEnumerator() | Sort-Object -Property Key | Where-Object { $_.Key -match "Arg.+" } | % { 101 | Write-Output "* $($_.Key) of type $($_.Value.ParameterType)" 102 | } 103 | 104 | Write-Output "`nReceived arguments:" 105 | 106 | $PSBoundParameters.GetEnumerator() | Sort-Object -Property Key | % { 107 | Write-Output "* $($_.Key) ($($_.Value.GetType())):`n$($_.Value | ConvertTo-String)" 108 | } 109 | -------------------------------------------------------------------------------- /acceptance/files/environments/bolt/init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #Comment to change sha 3 | echo $PT_message 4 | -------------------------------------------------------------------------------- /acceptance/files/environments/bolt/init.bat: -------------------------------------------------------------------------------- 1 | @REM Comment to change sha 2 | @echo %PT_message% 3 | -------------------------------------------------------------------------------- /acceptance/files/environments/bolt/testing_file.txt: -------------------------------------------------------------------------------- 1 | ## TESTING BODY ## 2 | -------------------------------------------------------------------------------- /acceptance/files/environments/bolt/unix_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "TEST SCRIPT: $1" 3 | exit 0 4 | -------------------------------------------------------------------------------- /acceptance/files/environments/bolt/win_script.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "TEST SCRIPT: $($Args[0])" 2 | exit 0 3 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/facts.d/another.json: -------------------------------------------------------------------------------- 1 | { 2 | "another": "I'm" 3 | } -------------------------------------------------------------------------------- /acceptance/files/modules/basic/files/data: -------------------------------------------------------------------------------- 1 | file content -------------------------------------------------------------------------------- /acceptance/files/modules/basic/lib/puppet/feature/can_warn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/util/feature' 4 | 5 | Puppet.features.add(:can_warn) { true } 6 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/lib/puppet/functions/pid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Functions.create_function(:pid) do 4 | def pid 5 | Process.pid.to_s 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/lib/puppet/provider/warn/basic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet_x/util/warn' 4 | 5 | Puppet::Type.type(:warn).provide(:basic) do 6 | desc 'Basic implementation of the warn type' 7 | 8 | confine another: "I'm" 9 | confine feature: :can_warn 10 | 11 | def warn(msg) 12 | PuppetX::Util.warn(msg) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/lib/puppet/type/warn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:warn) do 4 | @doc = "Sends a warning message to the agent run-time log." 5 | 6 | newproperty(:message, idempotent: false) do 7 | desc "The message to be sent to the log." 8 | def sync 9 | provider.warn(should) 10 | end 11 | 12 | def retrieve 13 | :absent 14 | end 15 | 16 | def insync?(_is_value) 17 | false 18 | end 19 | 20 | defaultto { @resource[:name] } 21 | end 22 | 23 | newparam(:name) do 24 | desc "An arbitrary tag for your own reference; the name of the message." 25 | isnamevar 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/lib/puppet_x/util/warn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PuppetX 4 | module Util 5 | def self.warn(msg) 6 | Puppet.warning(msg) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class basic() { 2 | notify { 'hello world': } 3 | } 4 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/manifests/strict.pp: -------------------------------------------------------------------------------- 1 | class basic::strict() { 2 | $hash = { a => 1, a => 2 } 3 | notify { 'hello': message => $hash } 4 | } 5 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/manifests/strict_variables.pp: -------------------------------------------------------------------------------- 1 | class basic::strict_variables() { 2 | notify { 'hello': message => "hello ${some_var_name}" } 3 | } 4 | -------------------------------------------------------------------------------- /acceptance/files/modules/basic/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "author": "Anyone, really.", 4 | "license": "Apache-2.0", 5 | "source": "", 6 | "dependencies": [] 7 | } 8 | -------------------------------------------------------------------------------- /acceptance/lib/helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path(File.dirname(__FILE__)) 2 | 3 | require 'beaker-puppet' 4 | -------------------------------------------------------------------------------- /acceptance/lib/puppet/acceptance/environment_utils.rb: -------------------------------------------------------------------------------- 1 | module Puppet 2 | module Acceptance 3 | module EnvironmentUtils 4 | 5 | # generate a random string of 6 letters and numbers. NOT secure 6 | def random_string 7 | [*('a'..'z'),*('0'..'9')].shuffle[0,8].join 8 | end 9 | private :random_string 10 | 11 | # if the first test to call this has changed the environmentpath, this will cause trouble 12 | # maybe not the best idea to memoize this? 13 | def environmentpath 14 | @@memoized_environmentpath ||= master.puppet['environmentpath'] 15 | end 16 | 17 | # create a tmpdir to hold a temporary environment bound by puppet environment naming restrictions 18 | # symbolically link environment into environmentpath 19 | def mk_tmp_environment(environment) 20 | # add the tmp_environment to a set to ensure no collisions 21 | @@tmp_environment_set ||= Set.new 22 | deadman = 100; loop_num = 0 23 | while @@tmp_environment_set.include?(tmp_environment = environment.downcase + '_' + random_string) do 24 | break if (loop_num = loop_num + 1) > deadman 25 | end 26 | @@tmp_environment_set << tmp_environment 27 | tmpdir = File.join('','tmp',tmp_environment) 28 | 29 | on master, "mkdir -p #{tmpdir}/manifests #{tmpdir}/modules" 30 | 31 | # register teardown to remove the link below 32 | teardown do 33 | on master, "rm -rf #{File.join(environmentpath,tmp_environment)}" 34 | end 35 | 36 | # WARNING: this won't work with filesync (symlinked environments are not supported) 37 | on master, "ln -sf #{tmpdir} #{File.join(environmentpath,tmp_environment)}" 38 | return tmp_environment 39 | end 40 | module_function :mk_tmp_environment, :environmentpath 41 | 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /acceptance/setup/common/045_SetPuppetServerOnAgents.rb: -------------------------------------------------------------------------------- 1 | test_name 'Configure puppet server on each agent' 2 | 3 | on agents, puppet("config set server #{master} --section main") 4 | -------------------------------------------------------------------------------- /acceptance/setup/common/050_Setup_Broker.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper' 2 | 3 | step 'Install build dependencies for broker' do 4 | # Assumes RedHat master 5 | master.install_package('make') 6 | master.install_package('git') 7 | master.install_package('java-11-openjdk-devel') 8 | end 9 | 10 | NUM_BROKERS = 2 11 | have_broker_replica = NUM_BROKERS > 1 12 | 13 | PCP_BROKER_FORK = ENV['PCP_BROKER_FORK'] || nil 14 | PCP_BROKER_REF = ENV['PCP_BROKER_REF'] || 'refs/tags/1.4.0' 15 | 16 | step 'Terminate existing pcp-brokers if running' do 17 | kill_all_pcp_brokers(master) 18 | end 19 | 20 | step 'Clone pcp-broker to broker_hosts' do 21 | pcp_broker_url = build_git_url('pcp-broker', PCP_BROKER_FORK, nil, 'https') 22 | if PCP_BROKER_REF 23 | pcp_broker_url += '#' + PCP_BROKER_REF 24 | end 25 | 26 | clone_git_repo_on(master, GIT_CLONE_FOLDER, extract_repo_info_from(pcp_broker_url)) 27 | 28 | step 'Replace PCP test certs with the Puppet certs of this SUT' do 29 | on(master, puppet('config print ssldir')) do |result| 30 | puppet_ssldir = result.stdout.chomp 31 | broker_ssldir = "#{GIT_CLONE_FOLDER}/pcp-broker/test-resources/ssl" 32 | on master, "\\cp #{puppet_ssldir}/certs/#{master}.pem #{broker_ssldir}/certs/broker.example.com.pem" 33 | on master, "\\cp #{puppet_ssldir}/private_keys/#{master}.pem #{broker_ssldir}/private_keys/broker.example.com.pem" 34 | on master, "\\cp #{puppet_ssldir}/ca/ca_crt.pem #{broker_ssldir}/ca/ca_crt.pem" 35 | on master, "\\cp #{puppet_ssldir}/ca/ca_crl.pem #{broker_ssldir}/ca/ca_crl.pem" 36 | end 37 | end 38 | 39 | on(master, "\\ln -s #{GIT_CLONE_FOLDER}/pcp-broker #{GIT_CLONE_FOLDER}/pcp-broker0") 40 | if have_broker_replica 41 | step 'create replica pcp-broker dir' do 42 | for i in 1..NUM_BROKERS-1 43 | on(master, "\\cp -a #{GIT_CLONE_FOLDER}/pcp-broker #{GIT_CLONE_FOLDER}/pcp-broker#{i}") 44 | end 45 | end 46 | end 47 | end 48 | 49 | step 'Download lein bootstrap' do 50 | on master, 'cd /usr/bin && '\ 51 | 'curl -O https://raw.githubusercontent.com/technomancy/leiningen/2.9.0/bin/lein' 52 | end 53 | 54 | step 'Run lein once so it sets itself up' do 55 | on master, 'chmod a+x /usr/bin/lein && export LEIN_ROOT=ok && /usr/bin/lein' 56 | end 57 | 58 | step 'Run lein deps to download dependencies' do 59 | # 'lein tk' will download dependencies automatically, but downloading them will take 60 | # some time and will eat into the polling period we allow for the broker to start 61 | for i in 0..NUM_BROKERS-1 62 | lein_command = "cd #{GIT_CLONE_FOLDER}/pcp-broker#{i}; export LEIN_ROOT=ok; lein with-profile #{LEIN_PROFILE} deps" 63 | if master[:gke_container] 64 | jvm_opts = ['-Xms2g -Xmx2g'] 65 | lein_command = "export JVM_OPTS='#{jvm_opts.join(' ')}'; export LEIN_JVM_OPTS='#{jvm_opts.join(' ')}'; #{lein_command}" 66 | end 67 | on(master, lein_command) 68 | end 69 | end 70 | 71 | step "Run pcp-broker on master in trapperkeeper in background and wait for it to report status 'running'" do 72 | for i in 0..NUM_BROKERS-1 73 | broker_instance = i 74 | configure_pcp_broker(master,broker_instance) 75 | end 76 | broker_instance = 0 # 0 indexed 77 | run_pcp_broker(master, broker_instance) 78 | end 79 | -------------------------------------------------------------------------------- /acceptance/setup/common/060_Setup_PCP_Client.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper' 2 | require 'socket' 3 | require 'fileutils' 4 | 5 | test_name 'Set up SSL certs for pcp-client to use' 6 | 7 | step 'On master create certs for the host running pcp-client' do 8 | master_fqdn = on(master, 'facter fqdn').stdout.strip 9 | on master, puppet("config set server #{master_fqdn} --section main") 10 | 11 | hostname = Socket.gethostname.downcase 12 | on master, "puppetserver ca generate --certname #{hostname}" 13 | on(master, puppet('config print ssldir')) do |result| 14 | puppet_ssldir = result.stdout.chomp 15 | FileUtils.mkdir_p('tmp/ssl/private_keys') 16 | FileUtils.mkdir_p('tmp/ssl/public_keys') 17 | FileUtils.mkdir_p('tmp/ssl/certs') 18 | scp_from(master, "#{puppet_ssldir}/private_keys/#{hostname}.pem", 'tmp/ssl/private_keys') 19 | scp_from(master, "#{puppet_ssldir}/public_keys/#{hostname}.pem", 'tmp/ssl/public_keys') 20 | scp_from(master, "#{puppet_ssldir}/certs/#{hostname}.pem", 'tmp/ssl/certs') 21 | scp_from(master, "#{puppet_ssldir}/certs/ca.pem", 'tmp/ssl/certs') 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /acceptance/setup/common/070_Setup_Server_fixtures.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/acceptance/environment_utils.rb' 2 | 3 | test_name 'Set up puppetserver fixtures' do 4 | 5 | step 'stop puppetserver' do 6 | on master, puppet('resource service puppetserver ensure=stopped') 7 | end 8 | 9 | PUPPETSERVER_CONFIG_FILE = '/etc/puppetlabs/puppetserver/conf.d/webserver.conf' 10 | 11 | fixtures = File.absolute_path('files') 12 | test_env = 'bolt' 13 | env_path = '' 14 | 15 | extend Puppet::Acceptance::EnvironmentUtils 16 | step "Copy #{test_env} environment fixtures to server" do 17 | fixture_env = File.join(fixtures, 'environments', test_env) 18 | filename = 'testing_file.txt' 19 | env_path = File.join(environmentpath, test_env) 20 | on(master, "mkdir -p #{env_path}") 21 | rsync_to(master, fixture_env, env_path) 22 | end 23 | 24 | step "Setup static file mount on puppetserver for #{test_env} environment" do 25 | hocon_file_edit_in_place_on(master, PUPPETSERVER_CONFIG_FILE) do |host, doc| 26 | doc.set_config_value("webserver.static-content", Hocon::ConfigValueFactory.from_any_ref([{ 27 | "path" => "/#{test_env}", 28 | "resource" => env_path 29 | }])) 30 | end 31 | end 32 | 33 | step 'start puppetserver' do 34 | on master, puppet('resource service puppetserver ensure=running') 35 | end 36 | end -------------------------------------------------------------------------------- /acceptance/tests/failover/intentional_shutdown_failover.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/config_helper.rb' 2 | require 'pxp-agent/test_helper.rb' 3 | 4 | test_name 'C97934 - agent should use next broker if primary is intentionally shutdown' do 5 | 6 | tag 'audit:high', # broker failover connection behavior is critical 7 | 'audit:acceptance' 8 | 9 | PRIMARY_BROKER_INSTANCE = 0 10 | REPLICA_BROKER_INSTANCE = 1 11 | teardown do 12 | kill_all_pcp_brokers(master) 13 | run_pcp_broker(master, PRIMARY_BROKER_INSTANCE) 14 | end 15 | 16 | step 'Ensure each agent host has pxp-agent configured with multiple uris, running and associated' do 17 | agents.each do |agent| 18 | on agent, puppet('resource service pxp-agent ensure=stopped') 19 | num_brokers = 2 20 | pxp_config = pxp_config_hash_using_puppet_certs(master, agent, num_brokers: num_brokers) 21 | create_remote_file(agent, pxp_agent_config_file(agent), to_hocon(pxp_config)) 22 | reset_logfile(agent) 23 | on agent, puppet('resource service pxp-agent ensure=running') 24 | 25 | assert(is_associated?(master, "pcp://#{agent}/agent"), 26 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's (#{broker_ws_uri(master)}) client inventory after ~#{PCP_INVENTORY_RETRIES} seconds") 27 | end 28 | end 29 | 30 | step 'Stop primary broker, start replica' do 31 | kill_all_pcp_brokers(master) 32 | run_pcp_broker(master, REPLICA_BROKER_INSTANCE) 33 | end 34 | 35 | step 'On each agent, test that a new association has occurred' do 36 | agents.each_with_index do |agent| 37 | assert(is_associated?(master, "pcp://#{agent}/agent"), 38 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's (#{broker_ws_uri(master)}) client inventory after ~#{PCP_INVENTORY_RETRIES} seconds") 39 | end 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /acceptance/tests/failover/timeout_failover.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/config_helper.rb' 2 | require 'pxp-agent/test_helper.rb' 3 | 4 | test_name 'C97964 - agent should use next broker if primary is timing out' do 5 | 6 | tag 'audit:high', # broker failover connection behavior is critical 7 | 'audit:acceptance' 8 | 9 | PRIMARY_BROKER_INSTANCE = 0 10 | REPLICA_BROKER_INSTANCE = 1 11 | teardown do 12 | unblock_pcp_broker(master,PRIMARY_BROKER_INSTANCE) 13 | kill_all_pcp_brokers(master) 14 | run_pcp_broker(master, PRIMARY_BROKER_INSTANCE) 15 | end 16 | 17 | step 'Ensure each agent host has pxp-agent configured with multiple uris, running and associated' do 18 | agents.each do |agent| 19 | on agent, puppet('resource service pxp-agent ensure=stopped') 20 | num_brokers = 2 21 | pxp_config = pxp_config_hash_using_puppet_certs(master, agent, num_brokers: num_brokers) 22 | # Should attempt to reconnect in ~10 seconds. 23 | pxp_config['allowed-keepalive-timeouts'] = 0 24 | pxp_config['ping-interval'] = 6 25 | create_remote_file(agent, pxp_agent_config_file(agent), to_hocon(pxp_config)) 26 | reset_logfile(agent) 27 | on agent, puppet('resource service pxp-agent ensure=running') 28 | 29 | assert(is_associated?(master, "pcp://#{agent}/agent"), 30 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's (#{broker_ws_uri(master)}) client inventory after ~#{PCP_INVENTORY_RETRIES} seconds") 31 | end 32 | end 33 | 34 | step 'Block primary broker, start replica' do 35 | block_pcp_broker(master,PRIMARY_BROKER_INSTANCE) 36 | run_pcp_broker(master, REPLICA_BROKER_INSTANCE) 37 | end 38 | 39 | step 'On each agent, test that a new association has occurred' do 40 | agents.each do |agent| 41 | inventory_retries = 60 42 | assert(is_associated?(master, "pcp://#{agent}/agent", inventory_retries), 43 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's (#{broker_ws_uri(master)}) client inventory after ~#{inventory_retries} seconds") 44 | end 45 | end 46 | 47 | # We do *not* need to ensure we are not associated with the primary broker 48 | # this requires the primary to receive socket close from the agent, which of course we have restricted above. 49 | # After opening the port the broker may or may not receive the socket close making this test flaky 50 | # In addition, we do not depend upon broker dis-association for any features. 51 | 52 | end 53 | -------------------------------------------------------------------------------- /acceptance/tests/multiple_pxp_agent_daemon.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/config_helper' 2 | require 'pxp-agent/test_helper' 3 | 4 | test_name 'Start pxp-agent daemon with pidfile present' do 5 | 6 | tag 'audit:high', # not honoring pid file would be significant issue 7 | 'audit:acceptance' 8 | 9 | applicable_agents = agents.select { |agent| agent['platform'] !~ /aix/ && agent['platform'] !~ /osx/ } 10 | unless applicable_agents.length > 0 then 11 | skip_test('OSX and AIX hosts use --foreground') 12 | end 13 | 14 | applicable_agents = applicable_agents.reject do |agent| 15 | on(agent, 'ls -l /proc/1/exe | grep \'systemd\'', :accept_all_exit_codes => true) do |result| 16 | result.stdout =~ /systemd/ 17 | end 18 | end 19 | unless applicable_agents.length > 0 then 20 | skip_test('systemd hosts use --foreground') 21 | end 22 | 23 | teardown do 24 | applicable_agents.each do |agent| 25 | on(agent, puppet('resource service pxp-agent ensure=running')) 26 | end 27 | end 28 | 29 | step 'Ensure each agent host has pxp-agent service running and enabled' do 30 | applicable_agents.each do |agent| 31 | on agent, puppet('resource service pxp-agent ensure=stopped') 32 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 33 | on agent, puppet('resource service pxp-agent ensure=running enable=true') 34 | 35 | assert(is_associated?(master, "pcp://#{agent}/agent"), 36 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 37 | end 38 | end 39 | 40 | applicable_agents.each do |agent| 41 | pids = [] 42 | 43 | step 'Start new pxp-agent service in daemon mode' do 44 | pids = get_process_pids(agent, 'pxp-agent') 45 | assert_equal(1, pids.count, 'Only one pxp-agent process should be running') 46 | 47 | alternate_log_dir = agent.tmpdir('alternate_log') 48 | alternate_log_file = File.join(alternate_log_dir, 'pxp-agent.log') 49 | pxp_agent_bindir = agent['privatebindir'].gsub('sys/ruby', 'pxp-agent') 50 | libs_bindir = agent['privatebindir'].gsub('sys/ruby', 'puppet') 51 | bindirs = "#{pxp_agent_bindir}:#{libs_bindir}:#{agent['privatebindir']}" 52 | on(agent, "env PATH=\"#{bindirs}:${PATH}\" pxp-agent --logfile #{alternate_log_file}", :acceptable_exit_codes => [1]) 53 | 54 | # Check log file before process. Once it contains the message, the new pxp-agent should be stopped. 55 | if windows?(agent) 56 | expect_file_on_host_to_contain(agent, alternate_log_file, /Unable to acquire process lock/) 57 | else 58 | expect_file_on_host_to_contain(agent, alternate_log_file, /Already running /) 59 | end 60 | 61 | assert_equal(pids, get_process_pids(agent, 'pxp-agent'), 'Only the original pxp-agent process should be running') 62 | end 63 | 64 | step 'Kill -9 old pxp-agent service and start service' do 65 | if windows?(agent) 66 | on agent, "taskkill /F /pid #{pids[0]}" 67 | else 68 | on agent, "kill -9 #{pids[0]}" 69 | end 70 | 71 | on(agent, puppet('resource service pxp-agent ensure=running')) 72 | assert(is_associated?(master, "pcp://#{agent}/agent")) 73 | refute_equal(pids, get_process_pids(agent, 'pxp-agent'), 'A new pxp-agent process should be running') 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_agent_associate.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | test_name 'C93807 - Associate pxp-agent with a PCP broker' 4 | 5 | tag 'audit:low', # this behavior is covered by necessity in other tests 6 | 'audit:acceptance' 7 | 8 | agents.each do |agent| 9 | 10 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 11 | 12 | step 'Stop pxp-agent if it is currently running' do 13 | on agent, puppet('resource service pxp-agent ensure=stopped') 14 | end 15 | 16 | step 'Assert that the agent is not listed in pcp-broker inventory' do 17 | assert(is_not_associated?(master, "pcp://#{agent}/agent"), 18 | "Agent identity pcp://#{agent}/agent for agent host #{agent} appears in pcp-broker's client inventory " \ 19 | "but pxp-agent service is supposed to be stopped") 20 | end 21 | 22 | step 'Start pxp-agent service' do 23 | on agent, puppet('resource service pxp-agent ensure=running') 24 | end 25 | 26 | step 'Assert that agent is listed in pcp-broker inventory' do 27 | assert(is_associated?(master, "pcp://#{agent}/agent"), 28 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's client inventory") 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_command/run_command.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | 3 | test_name 'run_command task' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | suts = agents.reject { |host| host['roles'].include?('master') } 9 | 10 | step 'Ensure each agent host has pxp-agent running and associated' do 11 | suts.each do |agent| 12 | on agent, puppet('resource service pxp-agent ensure=stopped') 13 | # don't capture /tmp/test.out in pxp-agent.log 14 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent, loglevel: "info")) 15 | on agent, puppet('resource service pxp-agent ensure=running') 16 | 17 | assert(is_associated?(master, "pcp://#{agent}/agent"), 18 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 19 | end 20 | end 21 | 22 | step 'Run a command (`echo`) on agent hosts' do 23 | suts.each do |agent| 24 | cmd = agent.platform =~ /windows/ ? 'write-host hello' : 'echo hello' 25 | run_successful_command(master, [agent], cmd) do |stdout| 26 | assert_equal('hello', stdout.chomp) 27 | end 28 | end 29 | end # test step 30 | 31 | step 'Responses that create large output which exceeds max-message-size error' do 32 | suts.each do |agent| 33 | # This test takes a bunch of resources to process correctly (and 34 | # not produce false positive failures). Platforms like MacOS, AIX 35 | # and older windows have a hard time reliably running the test 36 | # so we just confine the platforms to the standard Linuxes 37 | if agent.platform =~ /(el|ubuntu)/ 38 | teardown do 39 | on(agent, 'rm /tmp/test.out') 40 | end 41 | on(agent, '/opt/puppetlabs/puppet/bin/ruby -e "puts \'a\'* 70 * 1024 * 1024" > /tmp/test.out') 42 | cmd = 'cat /tmp/test.out' 43 | run_errored_command(master, [agent], cmd) do |stdout| 44 | assert_match(/exceeded max-message-size/, stdout) 45 | end 46 | end 47 | end 48 | end # test step 49 | end # test 50 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_script/run_script.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | require 'pxp-agent/config_helper.rb' 3 | 4 | suts = agents.reject { |host| host['roles'].include?('master') } 5 | 6 | def clean_files(agent) 7 | tasks_cache = get_tasks_cache(agent) 8 | assert_match(/ensure\s+=> 'absent'/, on(agent, puppet("resource file #{tasks_cache}/#{@sha256} ensure=absent force=true")).stdout) 9 | end 10 | 11 | test_name 'run script tests' do 12 | 13 | tag 'audit:high', # module validation: no other venue exists to test 14 | 'audit:acceptance' 15 | 16 | test_env = 'bolt' 17 | 18 | step 'Ensure each agent host has pxp-agent running and associated' do 19 | agents.each do |agent| 20 | on agent, puppet('resource service pxp-agent ensure=stopped') 21 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 22 | on agent, puppet('resource service pxp-agent ensure=running') 23 | 24 | assert(is_associated?(master, "pcp://#{agent}/agent"), 25 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 26 | end 27 | end 28 | 29 | # Spec testing can cover most of the functionality for running a script, 30 | # we only really require one test to ensure the script module is correctly 31 | # connected through the agent and is able to execute on a message actually 32 | # sent from a broker. 33 | step 'Execute a script run' do 34 | fixtures = File.absolute_path('files') 35 | fixture_env = File.join(fixtures, 'environments', test_env) 36 | 37 | suts.each do |agent| 38 | if windows?(agent) 39 | test_file = 'win_script.ps1' 40 | else 41 | test_file = 'unix_script.sh' 42 | end 43 | test_source = "/#{test_env}/#{test_file}" 44 | test_sha = Digest::SHA256.file(File.join(fixture_env, test_file)).hexdigest 45 | 46 | run_successful_script(master, agent, script_file_entry(test_file, test_sha, test_source), ['ARGS_TEST']) do |std_out| 47 | assert(std_out.include?("TEST SCRIPT: ARGS_TEST")) 48 | end 49 | 50 | teardown { 51 | clean_files(agent) 52 | } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_task/run_echo.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | 3 | test_name 'run echo task' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | step 'Ensure each agent host has pxp-agent running and associated' do 9 | agents.each do |agent| 10 | on agent, puppet('resource service pxp-agent ensure=stopped') 11 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 12 | on agent, puppet('resource service pxp-agent ensure=running') 13 | 14 | assert(is_associated?(master, "pcp://#{agent}/agent"), 15 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 16 | end 17 | end 18 | 19 | step 'Create echo task on agent hosts' do 20 | agents.each do |agent| 21 | if agent['platform'] =~ /win/ 22 | task_body = '@echo %PT_message%' 23 | else 24 | task_body = "#!/bin/sh\necho $PT_message" 25 | end 26 | 27 | @sha256 = create_task_on(agent, 'echo', 'init.bat', task_body) 28 | end 29 | end 30 | 31 | step 'Run echo task on agent hosts' do 32 | files = [task_file_entry('init.bat', @sha256)] 33 | run_successful_task(master, agents, 'echo', files, input: {:message => 'hello'}) do |stdout| 34 | assert_equal('hello', stdout.strip, "Output did not contain 'hello'") 35 | end 36 | end # test step 37 | end # test 38 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_task/run_multifile.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | 3 | test_name 'run multi file ruby task' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | step 'Ensure each agent host has pxp-agent running and associated' do 9 | agents.each do |agent| 10 | on agent, puppet('resource service pxp-agent ensure=stopped') 11 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 12 | on agent, puppet('resource service pxp-agent ensure=running') 13 | 14 | assert(is_associated?(master, "pcp://#{agent}/agent"), 15 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 16 | end 17 | end 18 | 19 | step 'Create ruby task on agent hosts' do 20 | agents.each do |agent| 21 | task_body = <<-'TASK' 22 | #!/opt/puppetlabs/puppet/bin/ruby 23 | require 'json' 24 | file1 = File.read(File.join(__dir__, '..', '..', 'file1.txt')) 25 | file2 = File.read(File.join(ENV['PT__installdir'], 'dir', 'file2.txt')) 26 | file3 = File.read(File.join(ENV['PT__installdir'], 'dir', 'sub_dir', 'file3.txt')) 27 | results = { "file1" => file1, "file2" => file2, "file3" => file3 } 28 | puts results.to_json 29 | TASK 30 | file1_body = "file1" 31 | file2_body = "file2" 32 | file3_body = "file3" 33 | @sha256_task = create_task_on(agent, 'multi_file', 'multi_file.rb', task_body) 34 | @sha256_file1 = create_task_on(agent, 'multi_file', 'file1.txt', file1_body) 35 | @sha256_file2 = create_task_on(agent, 'multi_file', 'file2.txt', file2_body) 36 | @sha256_file3 = create_task_on(agent, 'multi_file', 'file3.txt', file3_body) 37 | end 38 | end 39 | 40 | step 'Run ruby task on agent hosts' do 41 | 42 | files = [task_file_entry('multi_file.rb', @sha256_task), task_file_entry('file1.txt', @sha256_file1), task_file_entry('dir/file2.txt', @sha256_file2), task_file_entry('dir/sub_dir/file3.txt', @sha256_file3)] 43 | metadata = { implementations: [ 44 | { name: 'multi_file.rb', requirements: ['puppet-agent'], files: ['file1.txt']}], files: ['dir/']} 45 | run_successful_task(master, agents, 'multi_file', files, metadata: metadata ) do |stdout| 46 | 47 | json = stdout.delete("\r") 48 | assert_equal({"file1" => "file1\n", "file2" => "file2\n", "file3" => "file3\n"}, JSON.parse(json), "additional files not provided via '_installdir'") 49 | end 50 | end # test step 51 | end # test 52 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_task/run_ps1.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | require 'json' 3 | 4 | test_name 'run powershell task' do 5 | 6 | tag 'audit:high', # module validation: no other venue exists to test 7 | 'audit:acceptance' 8 | 9 | windows_hosts = hosts.select {|h| /windows/ =~ h[:platform]} 10 | if windows_hosts.empty? 11 | skip_test "No windows hosts to test powershell on" 12 | end 13 | 14 | step 'Ensure each agent host has pxp-agent running and associated' do 15 | windows_hosts.each do |agent| 16 | on agent, puppet('resource service pxp-agent ensure=stopped') 17 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 18 | on agent, puppet('resource service pxp-agent ensure=running') 19 | 20 | assert(is_associated?(master, "pcp://#{agent}/agent"), 21 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 22 | end 23 | end 24 | 25 | fixtures = File.absolute_path('files') 26 | 27 | step 'Create powershell task on agent hosts' do 28 | task_body = File.read(File.join(fixtures, 'complex-task.ps1')) 29 | windows_hosts.each do |agent| 30 | @sha256 = create_task_on(agent, 'echo', 'init.ps1', task_body) 31 | end 32 | end 33 | 34 | step 'Run powershell task on Windows agent hosts' do 35 | task_input = JSON.parse(File.read(File.join(fixtures, 'complex-args.json'))) 36 | task_output = File.read(File.join(fixtures, 'complex-output')) 37 | files = [task_file_entry('init.ps1', @sha256)] 38 | run_successful_task(master, windows_hosts, 'echo', files, input: task_input) do |stdout| 39 | # Handle some known variations 40 | stdout.gsub!(/System\.Guid/, 'guid') 41 | stdout.gsub!(/System\.TimeSpan/, 'timespan') 42 | 43 | assert_equal(task_output, stdout, "Output did not match") 44 | end 45 | end # test step 46 | 47 | step 'Try erroring powershell task on agent hosts' do 48 | task_body = 'throw "Error trying to do a task"' 49 | windows_hosts.each do |agent| 50 | @sha256 = create_task_on(agent, 'echo', 'init.ps1', task_body) 51 | end 52 | 53 | files = [task_file_entry('init.ps1', @sha256)] 54 | run_failed_task(master, windows_hosts, 'echo', files) do |stderr| 55 | assert_match(/Error trying to do a task/, stderr.delete("\n"), 'stderr did not contain error text') 56 | end 57 | end 58 | 59 | step 'Try non-zero exit powershell task on agent hosts' do 60 | # run_failed_task expects exit 1 61 | task_body = 'exit 1' 62 | windows_hosts.each do |agent| 63 | @sha256 = create_task_on(agent, 'echo', 'init.ps1', task_body) 64 | end 65 | 66 | files = [task_file_entry('init.ps1', @sha256)] 67 | run_failed_task(master, windows_hosts, 'echo', files) do |stderr| 68 | assert_equal(nil, stderr) 69 | end 70 | end 71 | end # test 72 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_task/run_puppet.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | 3 | test_name 'run puppet task' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | step 'Ensure each agent host has pxp-agent running and associated' do 9 | agents.each do |agent| 10 | on agent, puppet('resource service pxp-agent ensure=stopped') 11 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 12 | on agent, puppet('resource service pxp-agent ensure=running') 13 | 14 | assert(is_associated?(master, "pcp://#{agent}/agent"), 15 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 16 | end 17 | end 18 | 19 | step 'Create puppet task on agent hosts' do 20 | agents.each do |agent| 21 | # Note: shebang is hard 22 | # https://stackoverflow.com/questions/9988125/shebang-pointing-to-script-also-having-shebang-is-effectively-ignored 23 | # https://unix.stackexchange.com/questions/63979/shebang-line-with-usr-bin-env-command-argument-fails-on-linux 24 | if agent['platform'] =~ /osx/ 25 | shebang = '#!/opt/puppetlabs/puppet/bin/ruby /opt/puppetlabs/puppet/bin/puppet apply' 26 | elsif agent['platform'] =~ /el-5|solaris-|aix-/ 27 | shebang = '#!/usr/bin/env puppet-apply' 28 | create_remote_file(agent, '/usr/bin/puppet-apply', <<-EOF) 29 | #!/bin/sh 30 | exec /opt/puppetlabs/puppet/bin/puppet apply "$@" 31 | EOF 32 | on agent, 'chmod +x /usr/bin/puppet-apply' 33 | teardown { on agent, 'rm -f /usr/bin/puppet-apply' } 34 | else 35 | shebang = '#!/opt/puppetlabs/bin/puppet apply' 36 | end 37 | 38 | task_body = "#{shebang}\nnotify { 'hello': }" 39 | 40 | @sha256 = create_task_on(agent, 'hello', 'init.pp', task_body) 41 | end 42 | end 43 | 44 | step 'Run puppet task on agent hosts' do 45 | files = [task_file_entry('init.pp', @sha256)] 46 | run_successful_task(master, agents, 'hello', files, input: {:data => [1, 2, 3]}) do |stdout| 47 | assert_match(/Notify\[hello\]\/message: defined 'message' as 'hello'/, stdout, "Output did not contain 'hello'") 48 | end 49 | end # test step 50 | end # test 51 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_bolt_task/run_ruby.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/bolt_pxp_module_helper.rb' 2 | 3 | test_name 'run ruby task' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | step 'Ensure each agent host has pxp-agent running and associated' do 9 | agents.each do |agent| 10 | on agent, puppet('resource service pxp-agent ensure=stopped') 11 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 12 | on agent, puppet('resource service pxp-agent ensure=running') 13 | 14 | assert(is_associated?(master, "pcp://#{agent}/agent"), 15 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 16 | end 17 | end 18 | 19 | step 'Create ruby task on agent hosts' do 20 | agents.each do |agent| 21 | task_body = "#!/opt/puppetlabs/puppet/bin/ruby\nputs STDIN.gets\nputs ENV['PT_data']" 22 | @sha256 = create_task_on(agent, 'echo', 'init.rb', task_body) 23 | end 24 | end 25 | 26 | step 'Run ruby task on agent hosts' do 27 | files = [task_file_entry('init.rb', @sha256)] 28 | run_successful_task(master, agents, 'echo', files, input: {:data => [1, 2, 3]}) do |stdout| 29 | json, data = stdout.delete("\r").split("\n") 30 | assert_equal({"data" => [1,2,3], "_task" => "echo"}, JSON.parse(json), "Output did not contain 'data'") 31 | assert_equal('[1,2,3]', data, "Output did not contain 'data'") 32 | end 33 | end # test step 34 | end # test 35 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/negative/attempt_puppet_flag_whitelist_bypass.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | test_name 'C100245 - attempt bypass of flag whitelist with tab character' do 4 | 5 | confine :except, :platform => ['windows'] 6 | 7 | step 'pick test host' do 8 | @test_host = agents.first 9 | skip_test 'No compatible agents available to run test' unless @test_host 10 | skip_test 'Test not windows compatible' if @test_host.platform =~ /windows/ 11 | end 12 | 13 | step 'set up executable file' do 14 | @executable_path = File.join(@test_host.system_temp_path, 'run_me') 15 | @test_file = [@test_host.system_temp_path, (0...20).map { ('a'..'z').to_a[rand(26)] }.join].join('/') 16 | 17 | executable_contents =<<-EOS 18 | echo 'ALL YOUR BASE BELONG TO US NOW' >> #{@test_file} 19 | EOS 20 | 21 | create_remote_file(@test_host, @executable_path, executable_contents) 22 | on(@test_host, "chmod 777 #{@executable_path}") 23 | end 24 | 25 | teardown do 26 | on(@test_host, "rm -f #{@executable_path}", :accept_all_exit_codes => true) 27 | on(@test_host, "rm -f #{@test_file}", :accept_all_exit_codes => true) 28 | end 29 | 30 | step 'Ensure host has pxp-agent running and associated' do 31 | on @test_host, puppet('resource service pxp-agent ensure=stopped') 32 | create_remote_file(@test_host, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 33 | on @test_host, puppet('resource service pxp-agent ensure=running') 34 | 35 | assert(is_associated?(master, "pcp://#{@test_host}/agent"), 36 | "Agent #{@test_host} with PCP identity pcp://#{@test_host}/agent should be associated with pcp-broker") 37 | end 38 | 39 | step "Send an rpc_blocking_request to all agents" do 40 | target_identities = ["pcp://#{@test_host}/agent"] 41 | begin 42 | rpc_request(master, target_identities, 43 | 'pxp-module-puppet', 'run', 44 | {:env => [], :flags => ['--noop', 45 | '--onetime', 46 | '--no-daemonize', 47 | "\t--postrun_command #{@executable_path}"] 48 | }) 49 | rescue => exception 50 | raise("Exception occurred when trying to run Puppet on the test agent: #{exception.message}") 51 | end 52 | end 53 | 54 | step 'check if postrun command was run' do 55 | on(@test_host, "test -f #{@test_file}", :accept_all_exit_codes => true) do |result| 56 | assert_equal(1, result.exit_code, 'Flag whitelist failed to prevent remote code execution') 57 | end 58 | end 59 | end # test 60 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/rerun_transaction.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | 4 | STATUS_QUERY_MAX_RETRIES = 60 5 | STATUS_QUERY_INTERVAL_SECONDS = 1 6 | 7 | test_name 'C99777 - two runs with same transaction_id' do 8 | 9 | tag 'audit:high', 10 | 'audit:acceptance' 11 | 12 | step 'Ensure each agent host has pxp-agent running and associated' do 13 | agents.each do |agent| 14 | on agent, puppet('resource service pxp-agent ensure=stopped') 15 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 16 | on agent, puppet('resource service pxp-agent ensure=running') 17 | assert(is_associated?(master, "pcp://#{agent}/agent"), 18 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 19 | end 20 | end 21 | 22 | target_identities = agents.map {|agent| "pcp://#{agent}/agent"} 23 | transaction_ids = [] 24 | run_results = [] 25 | 26 | step "run puppet to generate a transaction_id" do 27 | transaction_ids = start_puppet_non_blocking_request(master, target_identities) 28 | target_identities.zip(transaction_ids).each do |identity, transaction_id| 29 | run_results << check_puppet_non_blocking_response(identity, transaction_id, STATUS_QUERY_MAX_RETRIES, STATUS_QUERY_INTERVAL_SECONDS, "unchanged") 30 | end 31 | end 32 | 33 | step "restart pxp" do 34 | agents.each do |agent| 35 | on agent, puppet('resource service pxp-agent ensure=stopped') 36 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 37 | on agent, puppet('resource service pxp-agent ensure=running') 38 | assert(is_associated?(master, "pcp://#{agent}/agent"), 39 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 40 | end 41 | end 42 | 43 | step "rerun with same transaction_id, expect provisional response and status queryable" do 44 | target_identities.zip(transaction_ids, run_results).each do |identity, transaction_id, first_run_result| 45 | # This should fail! 46 | response = rpc_request(master, [identity], 47 | 'pxp-module-puppet', 'run', 48 | {:flags => ['--onetime', 49 | '--no-daemonize', 50 | '--environment', 'production']}, 51 | false, 52 | transaction_id)[identity] 53 | 54 | assert_equal(response[:envelope][:message_type], 'http://puppetlabs.com/rpc_provisional_response', 'Did not receive rpc provisional response') 55 | second_run_result = check_puppet_non_blocking_response(identity, transaction_id, STATUS_QUERY_MAX_RETRIES, STATUS_QUERY_INTERVAL_SECONDS, "unchanged") 56 | assert_equal(first_run_result, second_run_result, 'Run results were not identical, Puppet may have run again') 57 | end 58 | end 59 | end # test 60 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/run_puppet.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | test_name 'C95972 - pxp-module-puppet run' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | step 'Ensure each agent host has pxp-agent running and associated' do 9 | agents.each do |agent| 10 | on agent, puppet('resource service pxp-agent ensure=stopped') 11 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 12 | on agent, puppet('resource service pxp-agent ensure=running') 13 | 14 | assert(is_associated?(master, "pcp://#{agent}/agent"), 15 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 16 | end 17 | end 18 | 19 | step "Send an rpc_blocking_request to all agents" do 20 | target_identities = [] 21 | agents.each do |agent| 22 | target_identities << "pcp://#{agent}/agent" 23 | end 24 | responses = nil # Declare here so not local to begin/rescue below 25 | begin 26 | responses = rpc_blocking_request(master, target_identities, 27 | 'pxp-module-puppet', 'run', 28 | {:env => [], :flags => ['--noop', 29 | '--onetime', 30 | '--no-daemonize'] 31 | }) 32 | rescue => exception 33 | fail("Exception occurred when trying to run Puppet on all agents: #{exception.message}") 34 | end 35 | agents.each do |agent| 36 | step "Check Run Puppet response for #{agent}" do 37 | identity = "pcp://#{agent}/agent" 38 | response = responses[identity] 39 | assert(response.has_key?(:envelope), "Response from PCP for #{agent} is missing :envelope") 40 | assert(response[:envelope].has_key?(:message_type), "Response from PCP for #{agent} is missing "\ 41 | ":message_type in :envelope") 42 | assert_equal('http://puppetlabs.com/rpc_blocking_response', 43 | response[:envelope][:message_type], 44 | "Received a message from pcp-broker for #{agent} but it wasn't of "\ 45 | "type http://puppetlabs.com/rpc_blocking_response") 46 | assert_equal(identity, 47 | response[:envelope][:sender], 48 | "Received the expected rpc_blocking_response for #{agent} "\ 49 | "but not from the expected identity") 50 | end # Step for this agent 51 | end # iterating on each agent 52 | end # test step 53 | end # test 54 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/run_puppet_agent_disabled.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | test_name 'C93065 - Run puppet and expect puppet agent disabled' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | teardown do 9 | on agents, puppet('agent --enable') 10 | end 11 | 12 | step 'Ensure each agent host has pxp-agent running and associated' do 13 | agents.each do |agent| 14 | on agent, puppet('resource service pxp-agent ensure=stopped') 15 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 16 | on agent, puppet('resource service pxp-agent ensure=running') 17 | 18 | assert(is_associated?(master, "pcp://#{agent}/agent"), 19 | "At the start of the test, #{agent} (with PCP identity pcp://#{agent}/agent ) should be associated with pcp-broker") 20 | end 21 | end 22 | 23 | step 'Set puppet agent on agent hosts to disabled' do 24 | on agents, puppet('agent --disable') 25 | end 26 | 27 | step "Send an rpc_blocking_request to all agents" do 28 | target_identities = [] 29 | agents.each do |agent| 30 | target_identities << "pcp://#{agent}/agent" 31 | end 32 | responses = nil # Declare here so not local to begin/rescue below 33 | begin 34 | responses = rpc_blocking_request(master, target_identities, 35 | 'pxp-module-puppet', 'run', 36 | {:env => [], :flags => []} 37 | ) 38 | rescue => exception 39 | fail("Exception occurred when trying to run Puppet on all agents: #{exception.message}") 40 | end 41 | agents.each_with_index do |agent| 42 | step "Check Run Puppet response for #{agent}" do 43 | identity = "pcp://#{agent}/agent" 44 | action_result = responses[identity][:data]["results"] 45 | assert(action_result.has_key?('error_type'), 'Results from pxp-module-puppet should contain an \'error_type\' entry') 46 | assert_equal('agent_disabled', action_result['error_type'], 47 | 'error_type of pxp-module-puppet response should be \'agent_disabled\'') 48 | assert(action_result.has_key?('error'), 'Results from pxp-module-puppet should contain an \'error\' entry') 49 | assert_equal('Puppet agent is disabled', action_result['error'], 50 | 'error of pxp-module-puppet response should be \'Puppet agent is disabled\'') 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/run_puppet_during_puppet.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | require 'puppet/acceptance/environment_utils' 3 | 4 | SECONDS_TO_SLEEP = 500 # The test will use SIGALARM to end this as soon as required 5 | STATUS_QUERY_MAX_RETRIES = 60 6 | STATUS_QUERY_INTERVAL_SECONDS = 1 7 | 8 | test_name 'Run Puppet while a Puppet Agent run is in-progress, wait for completion' do 9 | 10 | tag 'audit:high', # module validation: no other venue exists to test 11 | 'audit:acceptance' 12 | 13 | extend Puppet::Acceptance::EnvironmentUtils 14 | 15 | env_name = test_file_name = File.basename(__FILE__, '.*') 16 | environment_name = mk_tmp_environment(env_name) 17 | 18 | step 'On master, create a new environment that will result in a slow run' do 19 | site_manifest = "#{environmentpath}/#{environment_name}/manifests/site.pp" 20 | create_sleep_manifest(master, site_manifest, SECONDS_TO_SLEEP) 21 | end 22 | 23 | step 'Ensure each agent host has pxp-agent running and associated' do 24 | agents.each do |agent| 25 | on agent, puppet('resource service pxp-agent ensure=stopped') 26 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 27 | on agent, puppet('resource service pxp-agent ensure=running') 28 | assert(is_associated?(master, "pcp://#{agent}/agent"), 29 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 30 | end 31 | end 32 | 33 | step 'Start long-running Puppet agent jobs' do 34 | agents.each do |agent| 35 | on agent, puppet('agent', '--test', '--environment', environment_name, '--server', "#{master}", 36 | '/dev/null 2>&1 & echo $!') 37 | end 38 | end 39 | 40 | step 'Wait until Puppet starts executing' do 41 | agents.each do |agent| 42 | wait_for_sleep_process(agent, SECONDS_TO_SLEEP) 43 | end 44 | end 45 | 46 | target_identities = [] 47 | agents.each do |agent| 48 | target_identities << "pcp://#{agent}/agent" 49 | end 50 | 51 | transaction_ids = [] 52 | step 'Run Puppet on agents' do 53 | transaction_ids = start_puppet_non_blocking_request(master, target_identities) 54 | end 55 | 56 | step 'Signal sleep process to end so 1st Puppet run will complete' do 57 | stop_sleep_process(agents, SECONDS_TO_SLEEP) 58 | end 59 | 60 | target_identities.zip(transaction_ids).each do |identity, transaction_id| 61 | step "Check response to non-blocking request for #{identity}" do 62 | check_puppet_non_blocking_response(identity, transaction_id, 63 | STATUS_QUERY_MAX_RETRIES, STATUS_QUERY_INTERVAL_SECONDS, 64 | 'unchanged') 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/run_puppet_result_changed.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | require 'puppet/acceptance/environment_utils' 3 | 4 | test_name 'C93062 - Run puppet and expect \'changed\' result' do 5 | 6 | tag 'audit:high', # module validation: no other venue exists to test 7 | 'audit:acceptance' 8 | 9 | extend Puppet::Acceptance::EnvironmentUtils 10 | 11 | env_name = test_file_name = File.basename(__FILE__, '.*') 12 | environment_name = mk_tmp_environment(env_name) 13 | 14 | step 'On master, create a new environment that will result in Changed' do 15 | site_manifest = "#{environmentpath}/#{environment_name}/manifests/site.pp" 16 | create_remote_file(master, site_manifest, <<-SITEPP) 17 | node default { 18 | notify {'Notify resources cause a Puppet run to have a \\'changed\\' outcome':} 19 | } 20 | SITEPP 21 | on(master, "chmod 644 #{site_manifest}") 22 | end 23 | 24 | step 'Ensure each agent host has pxp-agent running and associated' do 25 | agents.each do |agent| 26 | on agent, puppet('resource service pxp-agent ensure=stopped') 27 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 28 | on agent, puppet('resource service pxp-agent ensure=running') 29 | 30 | assert(is_associated?(master, "pcp://#{agent}/agent"), 31 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 32 | end 33 | end 34 | 35 | step "Send an rpc_blocking_request to all agents" do 36 | target_identities = [] 37 | agents.each do |agent| 38 | target_identities << "pcp://#{agent}/agent" 39 | end 40 | responses = nil # Declare here so not local to begin/rescue below 41 | begin 42 | responses = rpc_blocking_request(master, target_identities, 43 | 'pxp-module-puppet', 'run', 44 | {:env => [], :flags => ['--environment', environment_name]}) 45 | rescue => exception 46 | fail("Exception occurred when trying to run Puppet on all agents: #{exception.message}") 47 | end 48 | agents.each_with_index do |agent| 49 | step "Check Run Puppet response for #{agent}" do 50 | identity = "pcp://#{agent}/agent" 51 | action_result = responses[identity][:data]["results"] 52 | # The test's pass/fail criteria is only the value of 'status'. However, if something goes wrong and Puppet needs to default 53 | # the environment to 'production' and results in 'unchanged' then it's better to fail specifically on the environment. 54 | assert(action_result.has_key?('environment'), "Results for pxp-module-puppet run on #{agent} should contain an 'environment' field") 55 | assert_equal(environment_name, action_result['environment'], "Result of pxp-module-puppet run on #{agent} should run with the "\ 56 | "#{environment_name} environment") 57 | assert(action_result.has_key?('status'), "Results for pxp-module-puppet run on #{agent} should contain a 'status' field") 58 | assert_equal('changed', action_result['status'], "Result of pxp-module-puppet run on #{agent} should be 'changed'") 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /acceptance/tests/pxp_module_puppet/run_puppet_v1.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/test_helper.rb' 2 | 3 | test_name 'pxp-module-puppet run with PCP v1' do 4 | 5 | tag 'audit:high', # module validation: no other venue exists to test 6 | 'audit:acceptance' 7 | 8 | skip_test('Already using PCP version 1') if ENV['PCP_VERSION'] == '1' 9 | 10 | # On teardown, restore v2 config file on each agent 11 | teardown do 12 | agents.each do |agent| 13 | on agent, puppet('resource service pxp-agent ensure=stopped') 14 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 15 | on agent, puppet('resource service pxp-agent ensure=running') 16 | end 17 | end 18 | 19 | step 'Ensure each agent host has pxp-agent running and associated with PCPv1' do 20 | agents.each do |agent| 21 | on agent, puppet('resource service pxp-agent ensure=stopped') 22 | pxp_config = pxp_config_hash_using_puppet_certs(master, agent) 23 | pxp_config['pcp-version'] = '1' 24 | pxp_config['broker-ws-uris'] = [broker_ws_uri(master, 1)] 25 | create_remote_file(agent, pxp_agent_config_file(agent), to_hocon(pxp_config)) 26 | on agent, puppet('resource service pxp-agent ensure=running') 27 | assert(is_associated?(master, "pcp://#{agent}/agent"), 28 | "Agent #{agent} with PCP identity pcp://#{agent}/agent should be associated with pcp-broker") 29 | end 30 | end 31 | 32 | step "Send an rpc_blocking_request to all agents" do 33 | target_identities = [] 34 | agents.each do |agent| 35 | target_identities << "pcp://#{agent}/agent" 36 | end 37 | responses = nil # Declare here so not local to begin/rescue below 38 | begin 39 | responses = rpc_blocking_request(master, target_identities, 40 | 'pxp-module-puppet', 'run', 41 | {:env => [], :flags => ['--noop', 42 | '--onetime', 43 | '--no-daemonize'] 44 | }) 45 | rescue => exception 46 | fail("Exception occurred when trying to run Puppet on all agents: #{exception.message}") 47 | end 48 | agents.each do |agent| 49 | step "Check Run Puppet response for #{agent}" do 50 | identity = "pcp://#{agent}/agent" 51 | response = responses[identity] 52 | assert(response.has_key?(:envelope), "Response from PCP for #{agent} is missing :envelope") 53 | assert(response[:envelope].has_key?(:message_type), "Response from PCP for #{agent} is missing "\ 54 | ":message_type in :envelope") 55 | assert_equal('http://puppetlabs.com/rpc_blocking_response', 56 | response[:envelope][:message_type], 57 | "Received a message from pcp-broker for #{agent} but it wasn't of "\ 58 | "type http://puppetlabs.com/rpc_blocking_response") 59 | assert_equal(identity, 60 | response[:envelope][:sender], 61 | "Received the expected rpc_blocking_response for #{agent} "\ 62 | "but not from the expected identity") 63 | end # Step for this agent 64 | end # iterating on each agent 65 | end # test step 66 | end # test 67 | -------------------------------------------------------------------------------- /acceptance/tests/reconnect_after_broker_unavailable.rb: -------------------------------------------------------------------------------- 1 | require 'pxp-agent/config_helper.rb' 2 | require 'pxp-agent/test_helper.rb' 3 | 4 | test_name 'C94789 - An associated agent should automatically reconnect when the broker was temporarily unavailable' 5 | 6 | tag 'audit:medium', # broker failover connection behavior is critical 7 | # functionally, is this test a duplicate of failover/timeout_failover.rb ?? 8 | 'audit:acceptance' 9 | 10 | step 'Ensure each agent host has pxp-agent running and associated' do 11 | agents.each do |agent| 12 | on agent, puppet('resource service pxp-agent ensure=stopped') 13 | create_remote_file(agent, pxp_agent_config_file(agent), pxp_config_hocon_using_puppet_certs(master, agent)) 14 | reset_logfile(agent) 15 | on agent, puppet('resource service pxp-agent ensure=running') 16 | 17 | assert(is_associated?(master, "pcp://#{agent}/agent"), 18 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's client inventory") 19 | end 20 | end 21 | 22 | step 'On master, stop then restart the broker' do 23 | kill_all_pcp_brokers(master) 24 | run_pcp_broker(master) 25 | end 26 | 27 | step 'On each agent, test that a 2nd association has occurred' do 28 | agents.each_with_index do |agent| 29 | assert(is_associated?(master, "pcp://#{agent}/agent"), 30 | "Agent identity pcp://#{agent}/agent for agent host #{agent} does not appear in pcp-broker's client inventory") 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | LEATHERMAN_VERSION: 1.11.0 3 | CPP_PCP_CLIENT_VERSION: 1.7.6 4 | CPPHOCON_VERSION: 0.2.0 5 | install: 6 | - choco install -y mingw-w64 -Version 5.2.0 -source https://www.myget.org/F/puppetlabs 7 | - choco install -y cmake -Version 3.2.2 -source https://www.myget.org/F/puppetlabs 8 | - choco install -y gettext -Version 0.19.6 -source https://www.myget.org/F/puppetlabs 9 | - choco install -y pl-toolchain-x64 -Version 2015.12.01.1 -source https://www.myget.org/F/puppetlabs 10 | - choco install -y pl-boost-x64 -Version 1.58.0.2 -source https://www.myget.org/F/puppetlabs 11 | - choco install -y pl-openssl-x64 -Version 1.0.24.1 -source https://www.myget.org/F/puppetlabs 12 | - choco install -y pl-curl-x64 -Version 7.46.0.1 -source https://www.myget.org/F/puppetlabs 13 | - choco install -y pl-zlib-x64 -Version 1.2.8.1 -source https://www.myget.org/F/puppetlabs 14 | - choco install -y pester -Version 4.10.1 15 | 16 | # Minimize environment polution; previously we were linking against the wrong OpenSSL DLLs. 17 | # Include Ruby and Powershell for unit tests. 18 | - SET PATH=C:\tools\pl-build-tools\bin;C:\tools\mingw64\bin;C:\ProgramData\chocolatey\bin;C:\Ruby22-x64\bin;C:\Program Files\7-Zip;C:\Windows\system32;C:\Windows;C:\Windows\System32\WindowsPowerShell\v1.0 19 | - ps: rm -r C:\OpenSSL-Win64 20 | 21 | - ps: wget "https://github.com/puppetlabs/leatherman/releases/download/$env:LEATHERMAN_VERSION/leatherman.7z" -OutFile "$pwd\leatherman.7z" 22 | - ps: 7z.exe x leatherman.7z -oC:\tools | FIND /V "ing " 23 | 24 | - ps: wget "https://github.com/puppetlabs/cpp-pcp-client/releases/download/$env:CPP_PCP_CLIENT_VERSION/cpp-pcp-client.7z" -OutFile "$pwd\cpp-pcp-client.7z" 25 | - ps: 7z.exe x cpp-pcp-client.7z -oC:\tools | FIND /V "ing " 26 | 27 | - ps: wget "https://github.com/puppetlabs/cpp-hocon/releases/download/$env:CPPHOCON_VERSION/cpp-hocon.7z" -OutFile "$env:temp\cpp-hocon.7z" 28 | - ps: 7z.exe x $env:temp\cpp-hocon.7z -oC:\tools | FIND /V "ing " 29 | 30 | build_script: 31 | - ps: cmake -G "MinGW Makefiles" -DCMAKE_TOOLCHAIN_FILE="C:\tools\pl-build-tools\pl-build-toolchain.cmake" -DCMAKE_PREFIX_PATH="C:\tools\leatherman;C:\tools\cpp-pcp-client;C:\tools\cpp-hocon" -DCMAKE_INSTALL_PREFIX=C:\tools -DBOOST_STATIC=ON . 32 | - ps: mingw32-make install 33 | 34 | test_script: 35 | # DLLs in C:\Windows\system32 somehow get picked up first, despite PATH. Make local copies to override that behavior. 36 | - ps: cp C:\Tools\pl-build-tools\bin\libeay32.dll .\bin 37 | - ps: cp C:\Tools\pl-build-tools\bin\ssleay32.dll .\bin 38 | - ps: ctest -V 2>&1 | %{ if ($_ -is [System.Management.Automation.ErrorRecord]) { $_ | c++filt } else { $_ } } 39 | - ps: type ./acceptance/files/complex-args.json | ./exe/PowershellShim.ps1 ./acceptance/files/complex-task.ps1 40 | - ps: Invoke-Pester -EnableExit 41 | -------------------------------------------------------------------------------- /cmake/FindCPPHOCON.cmake: -------------------------------------------------------------------------------- 1 | include(FindDependency) 2 | find_dependency(CPPHOCON DISPLAY "cpp-hocon" HEADERS "hocon/config.hpp" LIBRARIES "libcpp-hocon.a") 3 | 4 | include(FeatureSummary) 5 | set_package_properties(CPPHOCON PROPERTIES DESCRIPTION "A C++ parser for the HOCON configuration language" URL "https://github.com/puppetlabs/cpp-hocon") 6 | set_package_properties(CPPHOCON PROPERTIES TYPE REQUIRED PURPOSE "Allows parsing of the pxp-agent config file.") 7 | -------------------------------------------------------------------------------- /cmake/Findcpp-pcp-client.cmake: -------------------------------------------------------------------------------- 1 | include(FindDependency) 2 | find_dependency(cpp-pcp-client 3 | DISPLAY "cpp-pcp-client" 4 | HEADERS "cpp-pcp-client/connector/connection.hpp" 5 | LIBRARIES "libcpp-pcp-client.so" "libcpp-pcp-client.dylib" "cpp-pcp-client" 6 | REQUIRED) 7 | -------------------------------------------------------------------------------- /exe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories( 2 | ../lib/inc # the libpxp-agent headers 3 | ${LEATHERMAN_INCLUDE_DIRS} 4 | ${HORSEWHISPERER_INCLUDE_DIRS} 5 | ${cpp-pcp-client_INCLUDE_DIR} 6 | ) 7 | 8 | add_executable(pxp-agent main.cc) 9 | target_link_libraries(pxp-agent libpxp-agent) 10 | install(TARGETS pxp-agent DESTINATION bin) 11 | 12 | install(FILES apply_ruby_shim.rb DESTINATION bin 13 | PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) 14 | 15 | set(EXECUTION_WRAPPER_LIBS ${Boost_LIBRARIES} ${LEATHERMAN_LIBRARIES}) 16 | if (CMAKE_SYSTEM_NAME MATCHES "AIX") 17 | find_package(Threads) 18 | list(APPEND EXECUTION_WRAPPER_LIBS ${CMAKE_THREAD_LIBS_INIT}) 19 | endif() 20 | 21 | add_executable(execution_wrapper execution_wrapper.cc) 22 | target_link_libraries(execution_wrapper ${EXECUTION_WRAPPER_LIBS}) 23 | install(TARGETS execution_wrapper DESTINATION bin) 24 | 25 | include_directories(${LEATHERMAN_CATCH_INCLUDE}) 26 | add_executable(execution_wrapper-tests tests/main.cc) 27 | target_link_libraries(execution_wrapper-tests ${EXECUTION_WRAPPER_LIBS}) 28 | 29 | # Copy powershell shim to bin so we can use with local testing. 30 | foreach(shim PowershellShim.ps1 PowershellShim-Helper.ps1) 31 | set(shim_local ${CMAKE_BINARY_DIR}/bin/${shim}) 32 | add_custom_command( 33 | OUTPUT ${shim_local} 34 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${shim} ${shim_local} 35 | DEPENDS ${shim}) 36 | add_custom_target(${shim} ALL DEPENDS ${shim_local}) 37 | install(FILES ${shim} DESTINATION bin 38 | PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) 39 | endforeach(shim) 40 | 41 | add_test( 42 | NAME "task\\ wrapper\\ tests" 43 | COMMAND "${EXECUTABLE_OUTPUT_PATH}/execution_wrapper-tests" 44 | ) 45 | -------------------------------------------------------------------------------- /exe/PowershellShim.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | #requires -version 2.0 3 | 4 | try { 5 | $here = (Split-Path -Parent $MyInvocation.MyCommand.Path) 6 | . $here\PowershellShim-Helper.ps1 7 | 8 | $private:tempArgs = Get-ContentAsJson ($input -join "") 9 | 10 | Write-Debug "`nTask-shim deserialized arguments to Hashtable:`n" 11 | 12 | if ($tempArgs) { 13 | $tempArgs.GetEnumerator() | % { 14 | Write-Debug "* $($_.Key) ($($_.Value.GetType())):`n$($_.Value | ConvertTo-String)" 15 | } 16 | } 17 | 18 | $allowedArgs = (Get-Command $args[0]).Parameters.Keys 19 | $private:taskArgs = @{} 20 | $private:tempArgs.Keys | ? { $allowedArgs -contains $_ } | % { $private:taskArgs[$_] = $private:tempArgs[$_] } 21 | 22 | & $args[0] @taskArgs 23 | 24 | } catch { 25 | Write-Error $_ 26 | if ($LASTEXITCODE -eq $null) { $LASTEXITCODE = 1 } 27 | } 28 | exit $LASTEXITCODE 29 | -------------------------------------------------------------------------------- /exe/README.md: -------------------------------------------------------------------------------- 1 | # PowerShell Shim Testing 2 | 3 | A simple smoke test can be run on any platform with PowerShell via 4 | ``` 5 | type ../acceptance/files/complex-args.json | ./PowershellShim.ps1 ../acceptance/files/complex-task.ps1 6 | ``` 7 | 8 | More complete tests can be run via [Pester](https://github.com/pester/Pester), which will exercise PowerShell 2 code paths. 9 | -------------------------------------------------------------------------------- /exe/execution_wrapper.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace lth_loc = leatherman::locale; 10 | namespace lth_jc = leatherman::json_container; 11 | namespace lth_exec = leatherman::execution; 12 | namespace lth_file = leatherman::file_util; 13 | namespace lth_util = leatherman::util; 14 | namespace fs = boost::filesystem; 15 | 16 | #ifndef _WIN32 17 | static const fs::perms FILE_PERMS { fs::owner_read | fs::owner_write | fs::group_read }; 18 | #endif 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | // Read JSON input from stdin. Input should take the following format: 23 | // { 24 | // "executable": "(path to the executable to run)", 25 | // "arguments": [...], 26 | // "input": "(string to pass to the executable on stdin)", 27 | // "stdout": "(filepath to write stdout to)", 28 | // "stderr": "(filepath to write stderr to)", 29 | // "exitcode": "(filepath to write exitcode to)" 30 | // } 31 | boost::nowide::cin >> std::noskipws; 32 | std::istream_iterator i_s_i(boost::nowide::cin), end; 33 | auto params = lth_jc::JsonContainer(std::string { i_s_i, end }); 34 | auto executable = params.get("executable"); 35 | int exitcode; 36 | 37 | try { 38 | auto exec = lth_exec::execute( 39 | executable, 40 | params.get>("arguments"), 41 | params.get("input"), 42 | params.get("stdout"), 43 | params.get("stderr"), 44 | {}, // environment 45 | nullptr, // PID callback 46 | 0, // timeout 47 | #ifndef _WIN32 48 | // Not used on Windows. We instead rely on inherited directory ACLs. 49 | FILE_PERMS, 50 | #endif 51 | lth_util::option_set { 52 | lth_exec::execution_options::thread_safe, 53 | lth_exec::execution_options::merge_environment, 54 | lth_exec::execution_options::allow_stdin_unread, 55 | lth_exec::execution_options::inherit_locale }); 56 | exitcode = exec.exit_code; 57 | } catch (lth_exec::execution_exception &e) { 58 | // Avoid atomic update to allow testing against /dev/stderr. There should never be 59 | // multiple processes trying to write this output. 60 | boost::nowide::ofstream ofs { params.get("stderr"), std::ios::binary }; 61 | #ifndef _WIN32 62 | fs::permissions(params.get("stderr"), FILE_PERMS); 63 | #endif 64 | ofs << lth_loc::format("Executable '{1}' failed to run: {2}", executable, e.what()); 65 | exitcode = 127; 66 | } 67 | 68 | // Write the exit code; at this point, stdout and stderr are already written 69 | #ifdef _WIN32 70 | lth_file::atomic_write_to_file(std::to_string(exitcode), params.get("exitcode")); 71 | #else 72 | lth_file::atomic_write_to_file(std::to_string(exitcode), params.get("exitcode"), 73 | FILE_PERMS, std::ios::binary); 74 | #endif 75 | 76 | return exitcode; 77 | } 78 | -------------------------------------------------------------------------------- /exe/tests/Echo.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [Parameter(Mandatory = $true)] 4 | [String] 5 | $Message 6 | ) 7 | 8 | Write-Output $Message 9 | -------------------------------------------------------------------------------- /exe/tests/PowershellShim.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Loading Shim' { 2 | Mock Add-Type { throw "Could not load file or assembly 'System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified." } 3 | 4 | It 'Throws a helpful error message' { 5 | { . $PSScriptRoot\..\PowershellShim-Helper.ps1 } | Should -Throw "PXP Agent could not load the assemblies needed for JSON parsing. Please install .NET Framework 3.5 or greater, or rewrite the task to use a different input method than 'powershell'." 6 | } 7 | } 8 | 9 | . $PSScriptRoot\..\PowershellShim-Helper.ps1 10 | 11 | Describe 'ConvertFrom-Json2 | ConvertFrom-PSCustomObject' { 12 | It 'Given an empty string, will return an empty object' { 13 | $result = '{}' | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 14 | $result | Should -Be $null 15 | } 16 | 17 | It 'Given boolean json, should return a hash containing a boolean' { 18 | $result = '{"boolKey": true}' | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 19 | $result.Count | Should -Be 1 20 | $result['boolKey'] | Should -Be $true 21 | } 22 | 23 | It 'Given integer json, should return a hash containing an int' { 24 | $result = '{"intKey": 5}' | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 25 | $result.Count | Should -Be 1 26 | $result['intKey'] | Should -Be 5 27 | } 28 | 29 | It 'Given float json, should return a hash containing a float' { 30 | $result = '{"floatKey": 5.5}' | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 31 | $result.Count | Should -Be 1 32 | $result['floatKey'] | Should -Be 5.5 33 | } 34 | 35 | It 'Given string json, should return a hash containing a string' { 36 | $result = '{"stringKey": "hello world"}' | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 37 | $result.Count | Should -Be 1 38 | $result['stringKey'] | Should -Be "hello world" 39 | } 40 | 41 | It 'Given array json, should return a hash containing the array' { 42 | $result = Get-ContentAsJson '{"arrayOfHashes": [{"name": "puppet", "version": 42}, {"name": "pe", "version": 2015}]}' 43 | $result.Count | Should -Be 1 44 | $arr = $result['arrayOfHashes'] 45 | $arr.Count | Should -Be 2 46 | $arr[0]['name'] | Should -Be 'puppet' 47 | $arr[0]['version'] | Should -Be 42 48 | $arr[1]['name'] | Should -Be 'pe' 49 | $arr[1]['version'] | Should -Be 2015 50 | } 51 | } 52 | 53 | Describe 'PowershellShim' { 54 | it 'Runs the powershell script with the parameters supplied as JSON' { 55 | $result = '{"message": "hello world"}' | & $PSScriptRoot\..\PowershellShim.ps1 $PSScriptRoot\Echo.ps1 56 | $result | Should -Be "hello world" 57 | } 58 | 59 | it 'Runs the powershell script with only the parameters it understands' { 60 | $result = '{"message": "hello world", "_task": "echo"}' | & $PSScriptRoot\..\PowershellShim.ps1 $PSScriptRoot\Echo.ps1 61 | $result | Should -Be "hello world" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ext/debian/pxp-agent.default: -------------------------------------------------------------------------------- 1 | # Defaults for pxp-agent - sourced by /etc/init.d/pxp-agent 2 | 3 | # Startup options 4 | #PXP_AGENT_OPTIONS=--loglevel=debug 5 | -------------------------------------------------------------------------------- /ext/osx/pxp-agent.newsyslog.conf: -------------------------------------------------------------------------------- 1 | # logfilename [owner:group] mode count size when flags [/pid_file] [sig_num] 2 | /var/log/puppetlabs/pxp-agent/pxp-agent.log : 640 30 * $D0 BZ /var/run/puppetlabs/pxp-agent.pid 31 3 | -------------------------------------------------------------------------------- /ext/osx/pxp-agent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EnvironmentVariables 6 | 7 | LANG 8 | en_US.UTF-8 9 | 10 | Label 11 | pxp-agent 12 | KeepAlive 13 | 14 | ProgramArguments 15 | 16 | /opt/puppetlabs/bin/pxp-agent 17 | --foreground 18 | 19 | RunAtLoad 20 | 21 | Disabled 22 | 23 | StandardErrorPath 24 | /var/log/puppetlabs/pxp-agent/pxp-agent.log 25 | StandardOutPath 26 | /var/log/puppetlabs/pxp-agent/pxp-agent.log 27 | 28 | 29 | -------------------------------------------------------------------------------- /ext/pxp-agent.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/puppetlabs/pxp-agent/*.log { 2 | daily 3 | missingok 4 | rotate 30 5 | compress 6 | delaycompress 7 | notifempty 8 | sharedscripts 9 | postrotate 10 | if [ -s /var/run/puppetlabs/pxp-agent.pid ]; then kill -USR2 `cat /var/run/puppetlabs/pxp-agent.pid`; fi 11 | endscript 12 | } 13 | -------------------------------------------------------------------------------- /ext/redhat/pxp-agent.sysconfig: -------------------------------------------------------------------------------- 1 | # You may specify parameters to the PXP Agent here 2 | #PXP_AGENT_OPTIONS=--loglevel=debug 3 | -------------------------------------------------------------------------------- /ext/solaris/smf/pxp-agent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ext/systemd/pxp-agent.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/puppetlabs/pxp-agent/*.log { 2 | daily 3 | missingok 4 | rotate 30 5 | compress 6 | delaycompress 7 | notifempty 8 | sharedscripts 9 | postrotate 10 | systemctl is-active --quiet pxp-agent.service && systemctl kill --signal=USR2 --kill-who=main pxp-agent.service 11 | endscript 12 | } 13 | -------------------------------------------------------------------------------- /ext/systemd/pxp-agent.service: -------------------------------------------------------------------------------- 1 | # 2 | # Local settings can be configured without being overwritten by package upgrades, for example 3 | # if you want to increase pxp-agent open-files-limit to 10000, 4 | # you need to increase systemd's LimitNOFILE setting, so create a file named 5 | # "/etc/systemd/system/pxp-agent.service.d/limits.conf" containing: 6 | # [Service] 7 | # LimitNOFILE=10000 8 | # You can confirm it worked by running systemctl daemon-reload 9 | # then running systemctl show pxp-agent | grep LimitNOFILE 10 | # 11 | [Unit] 12 | Description=PCP Execution Protocol (PXP) Agent 13 | After=syslog.target network.target 14 | 15 | [Service] 16 | EnvironmentFile=-/etc/sysconfig/pxp-agent 17 | EnvironmentFile=-/etc/default/pxp-agent 18 | ExecStart=/opt/puppetlabs/puppet/bin/pxp-agent $PXP_AGENT_OPTIONS --foreground 19 | KillMode=process 20 | 21 | [Install] 22 | WantedBy=multi-user.target 23 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/action_output.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_ACTION_OUTPUT_HPP 2 | #define SRC_AGENT_ACTION_OUTPUT_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace PXPAgent { 9 | 10 | struct ActionOutput { 11 | int exitcode; 12 | std::string std_out; 13 | std::string std_err; 14 | }; 15 | 16 | } // namespace PXPAgent 17 | 18 | #endif // SRC_AGENT_ACTION_OUTPUT_HPP 19 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/action_request.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_ACTION_REQUEST_HPP_ 2 | #define SRC_AGENT_ACTION_REQUEST_HPP_ 3 | 4 | #include 5 | 6 | #include // ParsedChunk 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace PXPAgent { 15 | 16 | class ActionRequest { 17 | public: 18 | struct Error : public std::runtime_error { 19 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 20 | }; 21 | 22 | /// Throws an ActionRequest::Error in case it fails to retrieve 23 | /// the data chunk from the specified ParsedChunks or in case of 24 | /// binary data (currently not supported). 25 | ActionRequest(RequestType type_, 26 | PCPClient::ParsedChunks parsed_chunks_); 27 | 28 | void setResultsDir(const std::string& results_dir) const; 29 | 30 | const RequestType& type() const; 31 | const std::string& id() const; 32 | const std::string& sender() const; 33 | const std::string& transactionId() const; 34 | const std::string& module() const; 35 | const std::string& action() const; 36 | const bool& notifyOutcome() const; 37 | const PCPClient::ParsedChunks& parsedChunks() const; 38 | const std::string& resultsDir() const; 39 | 40 | // The following accessors perform lazy initialization 41 | // The params entry is not required; in case it's not included 42 | // in the request, an empty JsonContainer object is returned 43 | const leatherman::json_container::JsonContainer& params() const; 44 | const std::string& paramsTxt() const; 45 | const std::string& prettyLabel() const; 46 | 47 | private: 48 | RequestType type_; 49 | std::string id_; 50 | std::string sender_; 51 | std::string transaction_id_; 52 | std::string module_; 53 | std::string action_; 54 | bool notify_outcome_; 55 | PCPClient::ParsedChunks parsed_chunks_; 56 | 57 | // Lazy initialized; no setter is available 58 | mutable leatherman::json_container::JsonContainer params_; 59 | mutable std::string params_txt_; 60 | mutable std::string pretty_label_; 61 | 62 | // This has its own setter - it's not part of request's state 63 | mutable std::string results_dir_; 64 | 65 | void init(); 66 | void validateFormat(); 67 | }; 68 | 69 | } // namespace PXPAgent 70 | 71 | #endif // SRC_AGENT_ACTION_REQUEST_HPP_ 72 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/action_response.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_ACTION_RESPONSE_HPP 2 | #define SRC_AGENT_ACTION_RESPONSE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace PXPAgent { 16 | 17 | class ActionResponse { 18 | public: 19 | struct Error : public std::runtime_error { 20 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 21 | }; 22 | 23 | enum class ResponseType { Blocking, NonBlocking, StatusOutput, RPCError }; 24 | 25 | ModuleType module_type; 26 | RequestType request_type; 27 | ActionOutput output; 28 | leatherman::json_container::JsonContainer action_metadata; 29 | std::string status_query_transaction; 30 | 31 | static leatherman::json_container::JsonContainer 32 | getMetadataFromRequest(const ActionRequest& request); 33 | 34 | ActionResponse(ModuleType module_type_, 35 | const ActionRequest& request_, 36 | std::string status_query_transaction_ = ""); 37 | 38 | // Throws an Error if action_metadata is invalid. 39 | ActionResponse(ModuleType module_type_, 40 | RequestType request_type_, 41 | ActionOutput output_, 42 | leatherman::json_container::JsonContainer&& action_metadata_); 43 | 44 | void setStatus(ActionStatus status); 45 | 46 | // Set results_are_valid to true in the action metadata and update 47 | // the specified entries. 48 | void setValidResultsAndEnd(leatherman::json_container::JsonContainer&& results, 49 | const std::string& execution_error = ""); 50 | 51 | // Set results_are_valid to false in the action metadata and 52 | // update the execution_error entry. 53 | void setBadResultsAndEnd(const std::string& execution_error); 54 | 55 | const std::string& prettyRequestLabel() const; 56 | 57 | // Returns true if specified JSON object is conform to the 58 | // action_metadata schema. 59 | static bool isValidActionMetadata( 60 | const leatherman::json_container::JsonContainer & metadata); 61 | 62 | // Returns true if action_metadata has all the required entries 63 | // (the entries created at initialization). 64 | bool valid() const; 65 | 66 | // Returns true if action_metadata has all the required entries 67 | // (the entries created at initialization) plus the entries 68 | // required to send a message of the specified type. 69 | bool valid(ResponseType response_type) const; 70 | 71 | // Returns a JSON object suitable to be used in the data content 72 | // of a PXP message of the specified response type. 73 | // Throws a PCPClient::JsonContainer::data_key_error in case a 74 | // required entry is missing. 75 | leatherman::json_container::JsonContainer 76 | toJSON(ResponseType response_type) const; 77 | 78 | private: 79 | mutable std::string pretty_request_label_; 80 | }; 81 | 82 | } // namespace PXPAgents 83 | 84 | #endif // SRC_AGENT_ACTION_RESPONSE_HPP 85 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/action_status.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_ACTION_STATUS_HPP_ 2 | #define SRC_AGENT_ACTION_STATUS_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | namespace PXPAgent { 8 | 9 | enum class ActionStatus { Unknown, Running, Success, Failure, Undetermined }; 10 | 11 | static const std::map ACTION_STATUS_NAMES { 12 | { ActionStatus::Unknown, "unknown" }, 13 | { ActionStatus::Running, "running" }, 14 | { ActionStatus::Success, "success" }, 15 | { ActionStatus::Failure, "failure" }, 16 | { ActionStatus::Undetermined, "undetermined" } }; 17 | 18 | static const std::map NAMES_OF_ACTION_STATUS { 19 | { "unknown", ActionStatus::Unknown }, 20 | { "running", ActionStatus::Running }, 21 | { "success", ActionStatus::Success }, 22 | { "failure", ActionStatus::Failure }, 23 | { "undetermined", ActionStatus::Undetermined } }; 24 | 25 | } // namespace PXPAgent 26 | 27 | #endif // SRC_AGENT_ACTION_STATUS_HPP_ 28 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/agent.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_ENDPOINT_H_ 2 | #define SRC_AGENT_ENDPOINT_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace PXPAgent { 13 | 14 | class PXPConnector; 15 | 16 | class Agent { 17 | public: 18 | struct Error : public std::runtime_error { 19 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 20 | }; 21 | 22 | struct PCPConfigurationError : public Error { 23 | explicit PCPConfigurationError(std::string const& msg) : Error(msg) {} 24 | }; 25 | 26 | struct WebSocketConfigurationError : public Error { 27 | explicit WebSocketConfigurationError(std::string const& msg) : Error(msg) {} 28 | }; 29 | 30 | struct FatalError : public Error { 31 | explicit FatalError(std::string const& msg) : Error(msg) {} 32 | }; 33 | 34 | Agent() = delete; 35 | 36 | // Configure the pxp-agent run by: 37 | // - instantiating PXPConnector; 38 | // - instantiating a RequestProcessor. 39 | // 40 | // Throw an Agent::WebSocketConfigurationError in case it fails to 41 | // determine the agent identity by inspecting the SSL certificate. 42 | Agent(const Configuration::Agent& agent_configuration); 43 | 44 | // Start the agent and loop indefinitely, by: 45 | // - registering message callbacks; 46 | // - connecting to the PCP broker; 47 | // - monitoring the state of the connection; 48 | // - re-establishing the connection when requested. 49 | // 50 | // Throw an Agent::WebSocketConfigurationError in case it fails to 51 | // set up the Websocket connection on this end (ex. TLS layer 52 | // error) or an Agent::FatelError in case of unexpected failures; 53 | // errors such as message sending failures are only logged. 54 | void start(); 55 | 56 | private: 57 | // PXP connector 58 | std::shared_ptr connector_ptr_; 59 | 60 | // Request Processor 61 | RequestProcessor request_processor_; 62 | 63 | // Ping interval in seconds 64 | uint32_t ping_interval_s_; 65 | 66 | // Callback for PCPClient::Connector handling incoming PXP 67 | // blocking requests; it will execute the requested action and, 68 | // once finished, reply to the sender with an PXP blocking 69 | // response containing the action outcome. 70 | void blockingRequestCallback(const PCPClient::v1::ParsedChunks&); 71 | 72 | // Callback for PCPClient::Connector handling incoming PXP 73 | // non-blocking requests; it will start a job for the requested 74 | // action and reply with a provisional response containing the job 75 | // id. The reults will be stored in files in spool-dir. 76 | // In case the request has the notify_outcome field flagged, it 77 | // will send a PXP non-blocking response containing the action 78 | // outcome when finished. 79 | void nonBlockingRequestCallback(const PCPClient::v1::ParsedChunks&); 80 | }; 81 | 82 | } // namespace PXPAgent 83 | 84 | #endif // SRC_AGENT_ENDPOINT_H_ 85 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/module.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_MODULE_HPP_ 2 | #define SRC_AGENT_MODULE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include // Validator 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace PXPAgent { 16 | 17 | class Module { 18 | public: 19 | struct Error : public std::runtime_error { 20 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 21 | }; 22 | 23 | struct LoadingError : public Error { 24 | explicit LoadingError(std::string const& msg) : Error(msg) {} 25 | }; 26 | 27 | struct ProcessingError : public Error { 28 | explicit ProcessingError(std::string const& msg) : Error(msg) {} 29 | }; 30 | 31 | std::string module_name; 32 | std::vector actions; 33 | PCPClient::Validator config_validator_; 34 | PCPClient::Validator input_validator_; 35 | PCPClient::Validator results_validator_; 36 | 37 | Module(); 38 | 39 | virtual ~Module() = default; 40 | 41 | /// Whether or not the module has the specified action. 42 | bool hasAction(const std::string& action_name); 43 | 44 | /// The type of the module. 45 | virtual ModuleType type() { return ModuleType::Internal; } 46 | 47 | /// Whether or not the module supports non-blocking / asynchronous requests. 48 | /// If any subclass overrides this method to return true, it must override 49 | /// the `processOutputAndUpdateMetadata()` method too. 50 | virtual bool supportsAsync() { return false; } 51 | 52 | /// Log information about the output of the performed action 53 | /// while validating the output. 54 | /// Update the metadata of the ActionResponse instance (the 55 | /// 'results_are_valid', 'status', and 'execution_error' entries 56 | /// will be set appropriately; 'end' will be set to the current 57 | /// time). 58 | /// This function does not throw a ProcessingError in case of 59 | /// invalid output on stdout; such failure is instead reported 60 | /// in the response object's metadata. 61 | virtual void processOutputAndUpdateMetadata(ActionResponse& response); 62 | 63 | /// Validate the output contained in the ActionResponse instance, 64 | /// by using the module's 'output' JSON schema. In case of errors, 65 | /// the response's metadata will be updated ('results_are_valid' 66 | /// will be set to false, 'execution_error' will be populated, and 67 | /// 'status' to Failure, but 'end' will not be updated). 68 | /// This member function does not throw validation errors. 69 | void validateOutputAndUpdateMetadata(ActionResponse& response); 70 | 71 | /// Call the specified action and validate its results. 72 | /// Return an ActionResponse instance containing the action 73 | /// output and the validation outcome. 74 | /// This function does not throw any error; possible errors 75 | /// occurred during the action execution or the output validation 76 | /// will be reported within the ActionOutput instance. 77 | ActionResponse executeAction(const ActionRequest& request); 78 | 79 | protected: 80 | /// Subclass implementations should throw a ProcessingError in 81 | /// case it fails to execute the action. 82 | virtual ActionResponse callAction(const ActionRequest& request) = 0; 83 | }; 84 | 85 | } // namespace PXPAgent 86 | 87 | #endif // SRC_AGENT_MODULE_HPP_ 88 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/module_cache_dir.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_UTIL_MODULE_CACHE_DIR_HPP_ 2 | #define SRC_UTIL_MODULE_CACHE_DIR_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace PXPAgent { 10 | 11 | class ModuleCacheDir { 12 | public: 13 | ModuleCacheDir() = delete; 14 | ModuleCacheDir(const ModuleCacheDir&) = delete; 15 | ModuleCacheDir& operator=(const ModuleCacheDir&) = delete; 16 | ModuleCacheDir(const std::string& cache_dir, 17 | const std::string& cache_dir_purge_ttl); 18 | 19 | boost::filesystem::path createCacheDir(const std::string& sha256); 20 | boost::filesystem::path getCachedFile(const std::vector& master_uris, 21 | uint32_t connect_timeout, 22 | uint32_t timeout, 23 | leatherman::curl::client& client, 24 | const boost::filesystem::path& cache_dir, 25 | leatherman::json_container::JsonContainer& file); 26 | 27 | boost::filesystem::path downloadFileFromMaster(const std::vector& master_uris, 28 | uint32_t connect_timeout, 29 | uint32_t timeout, 30 | leatherman::curl::client& client, 31 | const boost::filesystem::path& cache_dir, 32 | const boost::filesystem::path& destination, 33 | const leatherman::json_container::JsonContainer& file); 34 | 35 | unsigned int purgeCache(const std::string& ttl, 36 | std::vector ongoing_transactions, 37 | std::function purge_callback); 38 | 39 | std::string cache_dir_; 40 | std::string purge_ttl_; 41 | 42 | private: 43 | std::tuple downloadFileWithCurl(const std::vector& master_uris, 44 | uint32_t connect_timeout_s, 45 | uint32_t timeout_s, 46 | leatherman::curl::client& client, 47 | const boost::filesystem::path& file_path, 48 | const leatherman::json_container::JsonContainer& uri); 49 | 50 | std::string createUrlEndpoint(const leatherman::json_container::JsonContainer& uri); 51 | std::string calculateSha256(const std::string& path); 52 | PCPClient::Util::mutex cache_purge_mutex_; 53 | PCPClient::Util::mutex curl_mutex_; 54 | }; 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/module_type.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_MODULE_TYPE_HPP_ 2 | #define SRC_AGENT_MODULE_TYPE_HPP_ 3 | 4 | namespace PXPAgent { 5 | 6 | enum class ModuleType { Internal, External }; 7 | 8 | } // namespace PXPAgent 9 | 10 | #endif // SRC_AGENT_MODULE_TYPE_HPP_ 11 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/apply.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_APPLY_H_ 2 | #define SRC_MODULES_APPLY_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace PXPAgent { 13 | namespace Modules { 14 | 15 | class Apply : public PXPAgent::Util::BoltModule, public PXPAgent::Util::Purgeable { 16 | public: 17 | Apply(const boost::filesystem::path& exec_prefix, 18 | const std::vector& primary_uris, 19 | const std::string& ca, 20 | const std::string& crt, 21 | const std::string& key, 22 | const std::string& crl, 23 | const std::string& proxy, 24 | std::shared_ptr module_cache_dir, 25 | std::shared_ptr storage); 26 | 27 | Util::CommandObject buildCommandObject(const ActionRequest& request) override; 28 | 29 | /// Utility to purge files from the cache_dir that have surpassed the ttl. 30 | /// If a purge_callback is not specified, the boost filesystem's remove_all() will be used. 31 | /// Returns number of directories purged. 32 | unsigned int purge( 33 | const std::string& ttl, 34 | std::vector ongoing_transactions, 35 | std::function purge_callback = nullptr) override; 36 | 37 | private: 38 | std::vector primary_uris_; 39 | std::string ca_; 40 | std::string crt_; 41 | std::string key_; 42 | std::string crl_; 43 | std::string proxy_; 44 | }; 45 | 46 | } // namespace Modules 47 | } // namespace PXPAgent 48 | 49 | #endif // SRC_MODULES_APPLY_H_ 50 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/command.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_COMMAND_H_ 2 | #define SRC_MODULES_COMMAND_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace PXPAgent { 9 | namespace Modules { 10 | 11 | class Command : public PXPAgent::Util::BoltModule { 12 | public: 13 | Command(const boost::filesystem::path& exec_prefix, std::shared_ptr storage); 14 | Util::CommandObject buildCommandObject(const ActionRequest& request) override; 15 | }; 16 | 17 | } // namespace Modules 18 | } // namespace PXPAgent 19 | 20 | #endif // SRC_MODULES_COMMAND_H_ 21 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/echo.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_ECHO_H_ 2 | #define SRC_MODULES_ECHO_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace PXPAgent { 8 | namespace Modules { 9 | 10 | class Echo : public PXPAgent::Module { 11 | public: 12 | Echo(); 13 | 14 | private: 15 | ActionResponse callAction(const ActionRequest& request) override; 16 | }; 17 | 18 | } // namespace Modules 19 | } // namespace PXPAgent 20 | 21 | #endif // SRC_MODULES_ECHO_H_ 22 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/file.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_FILE_H_ 2 | #define SRC_MODULES_FILE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace PXPAgent { 18 | namespace Modules { 19 | 20 | class File : public PXPAgent::Util::BoltModule, public PXPAgent::Util::Purgeable { 21 | public: 22 | File(const std::vector& master_uris, 23 | const std::string& ca, 24 | const std::string& crt, 25 | const std::string& key, 26 | const std::string& crl, 27 | const std::string& proxy, 28 | uint32_t download_connect_timeout, 29 | uint32_t download_timeout, 30 | std::shared_ptr module_cache_dir, 31 | std::shared_ptr storage); 32 | 33 | /// Utility to purge files from the cache_dir that have surpassed the ttl. 34 | /// If a purge_callback is not specified, the boost filesystem's remove_all() will be used. 35 | /// Returns number of directories purged. 36 | unsigned int purge( 37 | const std::string& ttl, 38 | std::vector ongoing_transactions, 39 | std::function purge_callback = nullptr) override; 40 | 41 | private: 42 | std::vector master_uris_; 43 | 44 | uint32_t file_download_connect_timeout_, file_download_timeout_; 45 | 46 | leatherman::curl::client client_; 47 | 48 | // callAction is normally implemented in the BoltModule base class. However: 49 | // DownloadFile will not execute any external processes, so it does not need the 50 | // overhead from callAction to parse blocking/non-blocking and call extrenal 51 | // processes. 52 | // 53 | // DownloadFile re-implements callAction to provide the functionality to download 54 | // files. 55 | ActionResponse callAction(const ActionRequest& request) override; 56 | 57 | // Since DownloadFile overrides callAction there's no reason to define 58 | // buildCommandObject (since it will never be called) 59 | Util::CommandObject buildCommandObject(const ActionRequest& request) override { 60 | throw Module::ProcessingError(leatherman::locale::format("DownloadFile module does not implement buildCommandObject!")); 61 | return Util::CommandObject { 62 | "", 63 | {}, 64 | {}, 65 | "", 66 | nullptr 67 | }; 68 | } 69 | }; 70 | 71 | } // namespace Modules 72 | } // namespace PXPAgent 73 | 74 | #endif // SRC_MODULES_FILE_H_ 75 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/ping.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_PING_H_ 2 | #define SRC_MODULES_PING_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace PXPAgent { 8 | namespace Modules { 9 | 10 | class Ping : public PXPAgent::Module { 11 | public: 12 | Ping(); 13 | 14 | // NOTE(ale): public for testing 15 | /// Ping calculates the time it took for a message to travel from 16 | /// the controller to the agent. It will then add that duration 17 | /// and the current broker time in milliseconds to the response. 18 | leatherman::json_container::JsonContainer ping(const ActionRequest& request); 19 | 20 | private: 21 | ActionResponse callAction(const ActionRequest& request) override; 22 | }; 23 | 24 | } // namespace Modules 25 | } // namespace PXPAgent 26 | 27 | #endif // SRC_MODULES_PING_H_ 28 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/script.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_SCRIPT_H_ 2 | #define SRC_MODULES_SCRIPT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace PXPAgent { 14 | namespace Modules { 15 | 16 | class Script : public PXPAgent::Util::BoltModule, public PXPAgent::Util::Purgeable { 17 | public: 18 | Script(const boost::filesystem::path& exec_prefix, 19 | const std::vector& master_uris, 20 | const std::string& ca, 21 | const std::string& crt, 22 | const std::string& key, 23 | const std::string& crl, 24 | const std::string& proxy, 25 | uint32_t download_connect_timeout, 26 | uint32_t download_timeout, 27 | std::shared_ptr module_cache_dir, 28 | std::shared_ptr storage); 29 | 30 | Util::CommandObject buildCommandObject(const ActionRequest& request) override; 31 | 32 | /// Utility to purge files from the cache_dir that have surpassed the ttl. 33 | /// If a purge_callback is not specified, the boost filesystem's remove_all() will be used. 34 | /// Returns number of directories purged. 35 | unsigned int purge( 36 | const std::string& ttl, 37 | std::vector ongoing_transactions, 38 | std::function purge_callback = nullptr) override; 39 | 40 | private: 41 | std::vector master_uris_; 42 | 43 | uint32_t download_connect_timeout_, download_timeout_; 44 | 45 | leatherman::curl::client client_; 46 | }; 47 | 48 | } // namespace Modules 49 | } // namespace PXPAgent 50 | 51 | #endif // SRC_MODULES_SCRIPT_H_ 52 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/modules/task.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_MODULES_TASK_H_ 2 | #define SRC_MODULES_TASK_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace PXPAgent { 15 | namespace Modules { 16 | 17 | class Task : public PXPAgent::Util::BoltModule, public PXPAgent::Util::Purgeable { 18 | public: 19 | Task(const boost::filesystem::path& exec_prefix, 20 | const std::vector& primary_uris, 21 | const std::string& ca, 22 | const std::string& crt, 23 | const std::string& key, 24 | const std::string& crl, 25 | const std::string& proxy, 26 | uint32_t task_download_connect_timeout_s, 27 | uint32_t task_download_timeout_s, 28 | std::shared_ptr module_cache_dir, 29 | std::shared_ptr storage); 30 | 31 | 32 | /// Utility to purge tasks from the task_cache_dir that have surpassed the ttl. 33 | /// If a purge_callback is not specified, the boost filesystem's remove_all() will be used. 34 | /// Returns number of directories purged. 35 | unsigned int purge( 36 | const std::string& ttl, 37 | std::vector ongoing_transactions, 38 | std::function purge_callback = nullptr) override; 39 | 40 | std::set const& features() const; 41 | 42 | private: 43 | std::vector primary_uris_; 44 | 45 | uint32_t task_download_connect_timeout_, task_download_timeout_; 46 | 47 | std::set features_; 48 | 49 | leatherman::curl::client client_; 50 | 51 | boost::filesystem::path downloadMultiFile(std::vector const& files, 52 | std::set const& download_set, 53 | boost::filesystem::path const& spool_dir); 54 | 55 | leatherman::json_container::JsonContainer selectLibFile(std::vector const& files, 56 | std::string const& file_name); 57 | 58 | Util::CommandObject buildCommandObject(const ActionRequest& request) override; 59 | }; 60 | 61 | } // namespace Modules 62 | } // namespace PXPAgent 63 | 64 | #endif // SRC_MODULES_TASK_H_ 65 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/pxp_connector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace PCPClient { 9 | struct ParsedChunks; 10 | class Schema; 11 | } // namespace PCPClient 12 | 13 | namespace PXPAgent { 14 | 15 | class ActionRequest; 16 | class ActionResponse; 17 | 18 | using MessageCallback = std::function; 19 | 20 | // In case of failure, the send() methods will only log the failure; 21 | // no exception will be propagated. 22 | class PXPConnector { 23 | public: 24 | virtual ~PXPConnector() = default; 25 | 26 | virtual void sendPCPError(const std::string& request_id, 27 | const std::string& description, 28 | const std::vector& endpoints) = 0; 29 | 30 | virtual void sendPXPError(const ActionRequest& request, 31 | const std::string& description) = 0; 32 | 33 | // Asserts that the ActionResponse arg has all needed entries. 34 | virtual void sendPXPError(const ActionResponse& response) = 0; 35 | 36 | // Asserts that the ActionResponse arg has all needed entries. 37 | virtual void sendBlockingResponse(const ActionResponse& response, 38 | const ActionRequest& request) = 0; 39 | 40 | // Asserts that the ActionResponse arg has all needed entries. 41 | virtual void sendStatusResponse(const ActionResponse& response, 42 | const ActionRequest& request) = 0; 43 | 44 | // Asserts that the ActionResponse arg has all needed entries. 45 | virtual void sendNonBlockingResponse(const ActionResponse& response) = 0; 46 | 47 | virtual void sendProvisionalResponse(const ActionRequest& request) = 0; 48 | 49 | virtual void connect(int max_connect_attempts = 0) = 0; 50 | 51 | virtual void monitorConnection(uint32_t max_connect_attempts = 0, 52 | uint32_t connection_check_interval_s = 15) = 0; 53 | 54 | virtual void registerMessageCallback(const PCPClient::Schema& schema, 55 | MessageCallback callback) = 0; 56 | }; 57 | 58 | } // namespace PXPAgent 59 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/pxp_connector_v1.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace PXPAgent { 13 | 14 | class PXPConnectorV1 : public PCPClient::v1::Connector, public PXPConnector { 15 | public: 16 | PXPConnectorV1(const Configuration::Agent& agent_configuration, boost::nowide::ofstream* logstream); 17 | 18 | void sendPCPError(const std::string& request_id, 19 | const std::string& description, 20 | const std::vector& endpoints) override; 21 | 22 | void sendProvisionalResponse(const ActionRequest& request) override; 23 | 24 | void sendPXPError(const ActionRequest& request, 25 | const std::string& description) override; 26 | 27 | // Asserts that the ActionResponse arg has all needed entries. 28 | void sendPXPError(const ActionResponse& response) override; 29 | 30 | // Asserts that the ActionResponse arg has all needed entries. 31 | void sendBlockingResponse(const ActionResponse& response, 32 | const ActionRequest& request) override; 33 | 34 | // Asserts that the ActionResponse arg has all needed entries. 35 | void sendStatusResponse(const ActionResponse& response, 36 | const ActionRequest& request) override; 37 | 38 | // Asserts that the ActionResponse arg has all needed entries. 39 | void sendNonBlockingResponse(const ActionResponse& response) override; 40 | 41 | void connect(int max_connect_attempts = 0) override; 42 | 43 | void monitorConnection(uint32_t max_connect_attempts = 0, 44 | uint32_t connection_check_interval_s = 15) override; 45 | 46 | void registerMessageCallback(const PCPClient::Schema& schema, 47 | MessageCallback callback) override; 48 | 49 | private: 50 | uint32_t pcp_message_ttl_s; 51 | 52 | void sendBlockingResponse_(const ActionResponse::ResponseType& response_type, 53 | const ActionResponse& response, 54 | const ActionRequest& request); 55 | }; 56 | 57 | } // namespace PXPAgent 58 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/pxp_connector_v2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace PXPAgent { 11 | 12 | class PXPConnectorV2 : public PCPClient::v2::Connector, public PXPConnector { 13 | public: 14 | PXPConnectorV2(const Configuration::Agent& agent_configuration, boost::nowide::ofstream* logstream); 15 | 16 | void sendPCPError(const std::string& request_id, 17 | const std::string& description, 18 | const std::vector& endpoints) override; 19 | 20 | void sendProvisionalResponse(const ActionRequest& request) override; 21 | 22 | void sendPXPError(const ActionRequest& request, 23 | const std::string& description) override; 24 | 25 | // Asserts that the ActionResponse arg has all needed entries. 26 | void sendPXPError(const ActionResponse& response) override; 27 | 28 | // Asserts that the ActionResponse arg has all needed entries. 29 | void sendBlockingResponse(const ActionResponse& response, 30 | const ActionRequest& request) override; 31 | 32 | // Asserts that the ActionResponse arg has all needed entries. 33 | void sendStatusResponse(const ActionResponse& response, 34 | const ActionRequest& request) override; 35 | 36 | // Asserts that the ActionResponse arg has all needed entries. 37 | void sendNonBlockingResponse(const ActionResponse& response) override; 38 | 39 | void connect(int max_connect_attempts = 0) override; 40 | 41 | void monitorConnection(uint32_t max_connect_attempts = 0, 42 | uint32_t connection_check_interval_s = 15) override; 43 | 44 | void registerMessageCallback(const PCPClient::Schema& schema, 45 | MessageCallback callback) override; 46 | 47 | private: 48 | void sendBlockingResponse_(const ActionResponse::ResponseType& response_type, 49 | const ActionResponse& response, 50 | const ActionRequest& request); 51 | }; 52 | 53 | } // namespace PXPAgent 54 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/pxp_schemas.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_PXP_SCHEMAS_HPP_ 2 | #define SRC_AGENT_PXP_SCHEMAS_HPP_ 3 | 4 | #include 5 | 6 | namespace PXPAgent { 7 | namespace PXPSchemas { 8 | 9 | // PXP blocking transaction 10 | static const std::string BLOCKING_REQUEST_TYPE { 11 | "http://puppetlabs.com/rpc_blocking_request" }; 12 | static const std::string BLOCKING_RESPONSE_TYPE { 13 | "http://puppetlabs.com/rpc_blocking_response" }; 14 | PCPClient::Schema BlockingRequestSchema(); 15 | PCPClient::Schema BlockingResponseSchema(); 16 | 17 | // PXP non blocking transaction 18 | static const std::string NON_BLOCKING_REQUEST_TYPE { 19 | "http://puppetlabs.com/rpc_non_blocking_request" }; 20 | static const std::string NON_BLOCKING_RESPONSE_TYPE { 21 | "http://puppetlabs.com/rpc_non_blocking_response" }; 22 | static const std::string PROVISIONAL_RESPONSE_TYPE { 23 | "http://puppetlabs.com/rpc_provisional_response" }; 24 | PCPClient::Schema NonBlockingRequestSchema(); 25 | PCPClient::Schema NonBlockingResponseSchema(); 26 | PCPClient::Schema ProvisionalResponseSchema(); 27 | 28 | // PXP error 29 | static const std::string PXP_ERROR_MSG_TYPE { 30 | "http://puppetlabs.com/rpc_error_message" }; 31 | PCPClient::Schema PXPErrorSchema(); 32 | 33 | } // namespace PXPSchemas 34 | } // namespace PXPAgent 35 | 36 | #endif // SRC_AGENT_PXP_SCHEMAS_HPP_ 37 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/request_type.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_REQUEST_TYPE_HPP_ 2 | #define SRC_AGENT_REQUEST_TYPE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | namespace PXPAgent { 8 | 9 | enum class RequestType { Blocking, NonBlocking }; 10 | 11 | static const std::map REQUEST_TYPE_NAMES { 12 | { RequestType::Blocking, "blocking" }, 13 | { RequestType::NonBlocking, "non blocking" } }; 14 | 15 | } // namespace PXPAgent 16 | 17 | #endif // SRC_AGENT_REQUEST_TYPE_HPP_ 18 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/results_mutex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_RESULTS_MUTEX_HPP_ 2 | #define SRC_AGENT_RESULTS_MUTEX_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace PXPAgent { 12 | 13 | // NOTE(ale): this class does not provide general synchronizaion 14 | // functions; it provides a cache for named mutexes where it is 15 | // assumed that a single actor (e.g. the RequestProcessor instance) 16 | // is allowed to add and remove mutexes, whereas all actors (including 17 | // instances of the Transaction Status request handler) can lock the 18 | // cached mutexes 19 | 20 | class ResultsMutex { 21 | public: 22 | struct Error : public std::runtime_error { 23 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 24 | }; 25 | 26 | static ResultsMutex& Instance() { 27 | static ResultsMutex instance {}; 28 | return instance; 29 | } 30 | 31 | using Mutex = PCPClient::Util::mutex; 32 | using Mutex_Ptr = std::shared_ptr; 33 | using Lock = PCPClient::Util::unique_lock; 34 | using LockGuard = PCPClient::Util::lock_guard; 35 | 36 | // Singleton users should lock this class mutex before accessing 37 | // the cache. 38 | // Note that the singleton caches shared_ptrs to mutexes; because 39 | // of that, it is safe to use the mutex pointer returned by get() 40 | // after releasing the access_mtx lock, even if a concurrent 41 | // thread is removing such mutex from the cache. 42 | Mutex access_mtx; 43 | 44 | // Useful for testing 45 | void reset(); 46 | 47 | // Whether the specified transaction mutex exists 48 | bool exists(std::string const& transaction_id); 49 | 50 | // Throw Error if transaction_id does not exist 51 | Mutex_Ptr get(std::string const& transaction_id); 52 | 53 | // Throw Error if transaction_id exists 54 | void add(std::string const& transaction_id); 55 | 56 | // Throw Error if transaction_id does not exist 57 | void remove(std::string const& transaction_id); 58 | 59 | private: 60 | // Cache 61 | std::map mutexes_; 62 | 63 | // Private ctor 64 | ResultsMutex(); 65 | }; 66 | 67 | } // namespace PXPAgent 68 | 69 | #endif // SRC_AGENT_RESULTS_MUTEX_HPP_ 70 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/time.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_TIME_HPP 2 | #define SRC_AGENT_TIME_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // TODO(ale): consider moving some of this to leatherman 10 | 11 | namespace PXPAgent { 12 | 13 | // Holds a time point reference to a past instant and allows comparing 14 | // it to timestamps in ISO8601 format 15 | class Timestamp { 16 | public: 17 | struct Error : public std::runtime_error { 18 | explicit Error(std::string const& msg) : std::runtime_error(msg) {} 19 | }; 20 | 21 | boost::posix_time::ptime time_point; 22 | 23 | // If the specified time interval is not in the following format, 24 | // the ctor throws an Error: 25 | // Possible suffixes (ex. "14d"): 26 | // `d` - days 27 | // `h` - hours 28 | // `m` - minutes 29 | Timestamp(const std::string& past_duration); 30 | 31 | // Returns a ptime time point for a past instant that is old 32 | // as specified 33 | // Throws an Error in case past_duration is not in a valid format, 34 | // as described mentioned above 35 | static boost::posix_time::ptime getPastInstant(std::string past_duration); 36 | 37 | // Returns the number of minutes out of the specified duration 38 | // Throws an Error in case past_duration is not in a valid format, 39 | // as described mentioned above 40 | static unsigned int getMinutes(std::string past_duration); 41 | 42 | // Throws an Error in case the specified time string is not in 43 | // the extended ISO format (refer to boost date_time docs) 44 | static std::string convertToISO(std::string extended_ISO8601_time); 45 | 46 | // Throws an Error in case it fails to create a time point from 47 | // the specified extended ISO date time string 48 | bool isNewerThan(const std::string& extended_ISO8601_time); 49 | 50 | bool isNewerThan(const std::time_t&); 51 | }; 52 | 53 | } // namespace PXPAgents 54 | 55 | #endif // SRC_AGENT_TIME_HPP 56 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/bolt_helpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_UTIL_BOLT_HELPERS_HPP_ 2 | #define SRC_UTIL_BOLT_HELPERS_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace PXPAgent { 10 | namespace Util { 11 | 12 | // Hard-code interpreters on Windows. On non-Windows, we still rely on permissions and #! 13 | const std::map>(std::string)>> BUILTIN_INTERPRETERS { 14 | #ifdef _WIN32 15 | {".rb", [](std::string filename) { return std::pair> { 16 | "ruby", { filename } 17 | }; }}, 18 | {".pp", [](std::string filename) { return std::pair> { 19 | "puppet", { "apply", filename } 20 | }; }}, 21 | {".ps1", [](std::string filename) { return std::pair> { 22 | "powershell", 23 | { "-NoProfile", "-NonInteractive", "-NoLogo", "-ExecutionPolicy", "Bypass", "-File", filename } 24 | }; }} 25 | #endif 26 | }; 27 | 28 | void findExecutableAndArguments(const boost::filesystem::path& file, Util::CommandObject& cmd); 29 | void createDir(const boost::filesystem::path& dir); 30 | void createSymLink(const boost::filesystem::path& destination, const boost::filesystem::path& source); 31 | std::vector splitArguments(const std::string& raw_arg_list); 32 | } // namespace Util 33 | } // namespace PXPAgent 34 | 35 | #endif // SRC_UTIL_BOLT_HELPERS_HPP_ 36 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/bolt_module.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_UTIL_BOLT_MODULE_HPP_ 2 | #define SRC_UTIL_BOLT_MODULE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace PXPAgent { 14 | namespace Util { 15 | 16 | // CommandObject holds collected parameters for leatherman's execution methods 17 | struct CommandObject { 18 | std::string executable; 19 | std::vector arguments; 20 | std::map environment; 21 | std::string input; 22 | std::function pid_callback; 23 | }; 24 | 25 | // This module is a basis for PXP modules supporting bolt functionality 26 | class BoltModule : public PXPAgent::Module { 27 | public: 28 | BoltModule(const boost::filesystem::path &exec_prefix, 29 | std::shared_ptr storage, 30 | std::shared_ptr module_cache_dir) 31 | : exec_prefix_(exec_prefix), 32 | storage_(std::move(storage)), 33 | module_cache_dir_(std::move(module_cache_dir)) {} 34 | 35 | /// Whether the module supports non-blocking / asynchronous requests. 36 | bool supportsAsync() override { return true; } 37 | 38 | /// Log information about the output of the performed action 39 | /// while validating the output is valid UTF-8. 40 | /// Update the metadata of the ActionResponse instance (the 41 | /// 'results_are_valid', 'status', and 'execution_error' entries 42 | /// will be set appropriately; 'end' will be set to the current 43 | /// time). 44 | /// This function does not throw a ProcessingError in case of 45 | /// invalid output on stdout; such failure is instead reported 46 | /// in the response object's metadata. 47 | void processOutputAndUpdateMetadata(ActionResponse& response) override; 48 | 49 | // Construct a CommandObject based on an ActionRequest - all inheriting classes must implement this method. 50 | virtual CommandObject buildCommandObject(const ActionRequest& request) = 0; 51 | 52 | protected: 53 | boost::filesystem::path exec_prefix_; 54 | std::shared_ptr storage_; 55 | std::shared_ptr module_cache_dir_; 56 | 57 | // Execute a CommandObject synchronously 58 | virtual leatherman::execution::result run_sync(const CommandObject &cmd); 59 | 60 | // Execute a CommandObject asynchronously, spawning a new process 61 | virtual leatherman::execution::result run(const CommandObject &cmd); 62 | 63 | virtual void callBlockingAction( 64 | const ActionRequest& request, 65 | const Util::CommandObject &command, 66 | ActionResponse &response); 67 | 68 | virtual void callNonBlockingAction( 69 | const ActionRequest& request, 70 | const CommandObject &command, 71 | ActionResponse &response); 72 | 73 | ActionResponse callAction(const ActionRequest& request) override; 74 | }; 75 | 76 | } // namespace Util 77 | } // namespace PXPAgent 78 | 79 | #endif // SRC_UTIL_BOLT_MODULE_HPP_ 80 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/daemonize.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_AGENT_UTIL_DAEMONIZE_HPP_ 2 | #define SRC_AGENT_UTIL_DAEMONIZE_HPP_ 3 | 4 | #ifndef _WIN32 5 | #include 6 | #include 7 | #endif 8 | 9 | namespace PXPAgent { 10 | namespace Util { 11 | 12 | #ifdef _WIN32 13 | void daemonize(); 14 | void daemon_cleanup(); 15 | #else 16 | std::unique_ptr daemonize(); 17 | #endif 18 | 19 | } // namespace Util 20 | } // namespace PXPAgent 21 | 22 | #endif // SRC_AGENT_UTIL_DAEMONIZE_HPP_ 23 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/process.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_UTIL_PROCESS_HPP_ 2 | #define SRC_UTIL_PROCESS_HPP_ 3 | 4 | namespace PXPAgent { 5 | namespace Util { 6 | 7 | bool processExists(int pid); 8 | int getPid(); 9 | 10 | } // namespace Util 11 | } // namespace PXPAgent 12 | 13 | #endif // SRC_UTIL_PROCESS_HPP_ 14 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/purgeable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace PXPAgent { 9 | namespace Util { 10 | 11 | class Purgeable { 12 | public: 13 | Purgeable() {} 14 | Purgeable(std::string ttl) : ttl_(std::move(ttl)) {} 15 | 16 | const std::string& get_ttl() const 17 | { 18 | return ttl_; 19 | } 20 | 21 | virtual unsigned int purge( 22 | const std::string& ttl, 23 | std::vector ongoing_transactions, 24 | std::function purge_callback = nullptr) = 0; 25 | 26 | protected: 27 | static void defaultDirPurgeCallback(const std::string& dir_path) 28 | { 29 | boost::filesystem::remove_all(dir_path); 30 | } 31 | 32 | private: 33 | std::string ttl_; 34 | }; 35 | 36 | } // namespace Util 37 | } // namespace PXPAgent 38 | -------------------------------------------------------------------------------- /lib/inc/pxp-agent/util/utf8.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_UTIL_UTF8_HPP_ 2 | #define SRC_UTIL_UTF8_HPP_ 3 | 4 | namespace PXPAgent { 5 | namespace Util { 6 | bool isValidUTF8(std::string &s); 7 | } // namespace Util 8 | } // namespace PXPAgent 9 | 10 | #endif // SRC_UTIL_UTF8_HPP_ 11 | -------------------------------------------------------------------------------- /lib/src/configuration/posix/configuration.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.configuration.posix.configuration" 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace PXPAgent { 12 | 13 | namespace lth_loc = leatherman::locale; 14 | 15 | static void sigLogfileReopen(int signal) { 16 | LOG_INFO("Caught SIGUSR2 signal"); 17 | try { 18 | Configuration::Instance().reopenLogfiles(); 19 | } catch (const std::exception& e) { 20 | // NB(ale): Configuration::reopenLogfiles should be exception safe... 21 | LOG_ERROR("Failed to reopen logfile: {1}", e.what()); 22 | } 23 | } 24 | 25 | void configure_platform_file_logging() { 26 | // Add the SIGUSR2 handler 27 | // HERE(ale): we expect that daemonize() will not touch this 28 | if (signal(SIGUSR2, sigLogfileReopen) == SIG_ERR) { 29 | throw Configuration::Error { 30 | lth_loc::translate("failed to set the SIGUSR2 handler") }; 31 | } else { 32 | LOG_DEBUG("Successfully registered the SIGUSR2 handler to reopen " 33 | "the logfile"); 34 | } 35 | } 36 | 37 | } // namespace PXPAgent 38 | -------------------------------------------------------------------------------- /lib/src/configuration/windows/configuration.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace PXPAgent { 4 | 5 | void configure_platform_file_logging() { 6 | // pass 7 | } 8 | 9 | } // namespace PXPAgent 10 | -------------------------------------------------------------------------------- /lib/src/modules/echo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include // std::move 5 | 6 | namespace PXPAgent { 7 | namespace Modules { 8 | 9 | namespace lth_jc = leatherman::json_container; 10 | 11 | static const std::string ECHO { "echo" }; 12 | 13 | Echo::Echo() { 14 | module_name = ECHO; 15 | actions.push_back(ECHO); 16 | PCPClient::Schema input_schema { ECHO }; 17 | input_schema.addConstraint("argument", PCPClient::TypeConstraint::String, 18 | true); 19 | PCPClient::Schema output_schema { ECHO }; 20 | 21 | input_validator_.registerSchema(input_schema); 22 | results_validator_.registerSchema(output_schema); 23 | } 24 | 25 | ActionResponse Echo::callAction(const ActionRequest& request) { 26 | auto params = request.params(); 27 | 28 | assert(params.includes("argument") 29 | && params.type("argument") == lth_jc::DataType::String); 30 | 31 | ActionResponse response { ModuleType::Internal, request }; 32 | lth_jc::JsonContainer results {}; 33 | results.set("outcome", params.get("argument")); 34 | response.setValidResultsAndEnd(std::move(results)); 35 | return response; 36 | } 37 | 38 | } // namespace Modules 39 | } // namespace PXPAgent 40 | -------------------------------------------------------------------------------- /lib/src/modules/ping.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.modules.ping" 10 | #include 11 | 12 | namespace PXPAgent { 13 | namespace Modules { 14 | 15 | namespace lth_jc = leatherman::json_container; 16 | namespace lth_loc = leatherman::locale; 17 | 18 | static const std::string PING { "ping" }; 19 | 20 | Ping::Ping() { 21 | module_name = PING; 22 | actions.push_back(PING); 23 | PCPClient::Schema input_schema { PING }; 24 | input_schema.addConstraint("sender_timestamp", 25 | PCPClient::TypeConstraint::String); 26 | PCPClient::Schema output_schema { PING }; 27 | 28 | input_validator_.registerSchema(input_schema); 29 | results_validator_.registerSchema(output_schema); 30 | } 31 | 32 | lth_jc::JsonContainer Ping::ping(const ActionRequest& request) { 33 | lth_jc::JsonContainer data {}; 34 | 35 | if (request.parsedChunks().debug.empty()) { 36 | LOG_DEBUG("Found no debug entry in the request message"); 37 | data.set>("request_hops", {}); 38 | } else { 39 | auto& debug_entry = request.parsedChunks().debug[0]; 40 | 41 | try { 42 | data.set>( 43 | "request_hops", 44 | debug_entry.get>("hops")); 45 | } catch (lth_jc::data_parse_error& e) { 46 | LOG_ERROR("Failed to parse debug entry: {1}", e.what()); 47 | LOG_DEBUG("Debug entry: {1}", debug_entry.toString()); 48 | throw Module::ProcessingError { 49 | lth_loc::translate("debug entry is not valid JSON") }; 50 | } 51 | } 52 | return data; 53 | } 54 | 55 | ActionResponse Ping::callAction(const ActionRequest& request) { 56 | ActionResponse response { ModuleType::Internal, request }; 57 | response.setValidResultsAndEnd(ping(request)); 58 | return response; 59 | } 60 | 61 | } // namespace Modules 62 | } // namespace PXPAgent 63 | -------------------------------------------------------------------------------- /lib/src/pxp_schemas.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace PXPAgent { 4 | namespace PXPSchemas { 5 | 6 | // HERE(ale): this must be kept up to date with 7 | // https://github.com/puppetlabs/pcp-specifications 8 | 9 | using C_Type = PCPClient::ContentType; 10 | using T_Constraint = PCPClient::TypeConstraint; 11 | 12 | PCPClient::Schema BlockingRequestSchema() { 13 | PCPClient::Schema schema { BLOCKING_REQUEST_TYPE, C_Type::Json }; 14 | // NB: additionalProperties = false 15 | schema.addConstraint("transaction_id", T_Constraint::String, true); 16 | schema.addConstraint("module", T_Constraint::String, true); 17 | schema.addConstraint("action", T_Constraint::String, true); 18 | schema.addConstraint("params", T_Constraint::Object, false); 19 | return schema; 20 | } 21 | 22 | PCPClient::Schema BlockingResponseSchema() { 23 | PCPClient::Schema schema { BLOCKING_RESPONSE_TYPE, C_Type::Json }; 24 | // NB: additionalProperties = false 25 | schema.addConstraint("transaction_id", T_Constraint::String, true); 26 | schema.addConstraint("results", T_Constraint::Object, true); 27 | return schema; 28 | } 29 | 30 | PCPClient::Schema NonBlockingRequestSchema() { 31 | PCPClient::Schema schema { NON_BLOCKING_REQUEST_TYPE, C_Type::Json }; 32 | // NB: additionalProperties = false 33 | schema.addConstraint("transaction_id", T_Constraint::String, true); 34 | schema.addConstraint("notify_outcome", T_Constraint::Bool, true); 35 | schema.addConstraint("module", T_Constraint::String, true); 36 | schema.addConstraint("action", T_Constraint::String, true); 37 | schema.addConstraint("params", T_Constraint::Object, false); 38 | return schema; 39 | } 40 | 41 | PCPClient::Schema NonBlockingResponseSchema() { 42 | PCPClient::Schema schema { NON_BLOCKING_RESPONSE_TYPE, C_Type::Json }; 43 | // NB: additionalProperties = false 44 | schema.addConstraint("transaction_id", T_Constraint::String, true); 45 | schema.addConstraint("results", T_Constraint::Object, true); 46 | return schema; 47 | } 48 | 49 | PCPClient::Schema ProvisionalResponseSchema() { 50 | PCPClient::Schema schema { PROVISIONAL_RESPONSE_TYPE, C_Type::Json }; 51 | // NB: additionalProperties = false 52 | schema.addConstraint("transaction_id", T_Constraint::String, true); 53 | return schema; 54 | } 55 | 56 | PCPClient::Schema PXPErrorSchema() { 57 | PCPClient::Schema schema { PXP_ERROR_MSG_TYPE, C_Type::Json }; 58 | // NB: additionalProperties = false 59 | schema.addConstraint("transaction_id", T_Constraint::String, true); 60 | schema.addConstraint("id", T_Constraint::String, true); 61 | schema.addConstraint("description", T_Constraint::String, true); 62 | return schema; 63 | } 64 | 65 | } // namespace PXPAgent 66 | } // namespace PXPSchemas 67 | -------------------------------------------------------------------------------- /lib/src/results_mutex.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.results_mutex" 6 | #include 7 | 8 | namespace PXPAgent { 9 | 10 | namespace lth_loc = leatherman::locale; 11 | 12 | // Private ctor 13 | 14 | ResultsMutex::ResultsMutex() 15 | : access_mtx {}, 16 | mutexes_ {} { 17 | } 18 | 19 | // Public interface 20 | 21 | void ResultsMutex::reset() { 22 | LockGuard a_lck { access_mtx }; 23 | mutexes_.clear(); 24 | } 25 | 26 | bool ResultsMutex::exists(std::string const& transaction_id) { 27 | return (mutexes_.find(transaction_id) != mutexes_.end()); 28 | } 29 | 30 | ResultsMutex::Mutex_Ptr ResultsMutex::get(std::string const& transaction_id) { 31 | if (!exists(transaction_id)) 32 | throw Error { lth_loc::translate("does not exists") }; 33 | 34 | return mutexes_[transaction_id]; 35 | } 36 | 37 | void ResultsMutex::add(std::string const& transaction_id) { 38 | LOG_TRACE("Adding transaction id {1}", transaction_id); 39 | if (exists(transaction_id)) 40 | throw Error { lth_loc::translate("already exists") }; 41 | 42 | auto mtx = std::make_shared(); 43 | mutexes_.emplace(transaction_id, mtx); 44 | } 45 | 46 | void ResultsMutex::remove(std::string const& transaction_id) { 47 | LOG_TRACE("Removing transaction id {1}", transaction_id); 48 | if (!exists(transaction_id)) 49 | throw Error { lth_loc::translate("does not exist") }; 50 | 51 | mutexes_.erase(transaction_id); 52 | } 53 | 54 | 55 | } // namespace PXPAgent 56 | -------------------------------------------------------------------------------- /lib/src/util/posix/process.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include // getpid() 6 | 7 | namespace PXPAgent { 8 | namespace Util { 9 | 10 | // Checks the PID by sending NULL SIGNAL WITH kill(). 11 | // NB: does not consider recycled PIDs nor zombie processes. 12 | bool processExists(int pid) { 13 | if (kill(pid, 0)) { 14 | switch (errno) { 15 | case ESRCH: 16 | return false; 17 | case EPERM: 18 | // Process exists, but we can't signal to it 19 | return true; 20 | default: 21 | // Unexpected 22 | return false; 23 | } 24 | } 25 | 26 | return true; 27 | } 28 | 29 | int getPid() { 30 | return getpid(); 31 | } 32 | 33 | } // namespace Util 34 | } // namespace PXPAgent 35 | -------------------------------------------------------------------------------- /lib/src/util/utf8.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #if RAPIDJSON_MAJOR_VERSION > 1 || RAPIDJSON_MAJOR_VERSION == 1 && RAPIDJSON_MINOR_VERSION >= 1 3 | // Header for StringStream was added in rapidjson 1.1 in a backwards incompatible way. 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | namespace PXPAgent { 11 | namespace Util { 12 | bool isValidUTF8(std::string &s) { 13 | rapidjson::StringStream source(s.data()); 14 | rapidjson::InsituStringStream target(&s[0]); 15 | 16 | target.PutBegin(); 17 | while (source.Tell() < s.size()) { 18 | if (!rapidjson::UTF8::Validate(source, target)) { 19 | return false; 20 | } 21 | } 22 | 23 | // rapidjson::UTF8::Validate accepts null bytes as valid UTF-8. 24 | // They technically are valid since they're the null character. But 25 | // null characters aren't valid in a string, so we want to disallow 26 | // them regardless. 27 | bool has_null = std::any_of(s.begin(), s.end(), [](char c) { 28 | return c == 0; 29 | }); 30 | return !has_null; 31 | } 32 | } // namespace Util 33 | } // namespace PXPAgent 34 | -------------------------------------------------------------------------------- /lib/src/util/windows/daemonize.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.util.daemonize" 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace PXPAgent { 14 | namespace Util { 15 | 16 | namespace lth_win = leatherman::windows; 17 | 18 | BOOL WINAPI ctrl_handler(DWORD sig) 19 | { 20 | switch (sig) { 21 | case CTRL_C_EVENT: 22 | case CTRL_CLOSE_EVENT: 23 | case CTRL_SHUTDOWN_EVENT: 24 | { 25 | LOG_INFO("Caught signal {1} - shutting down", std::to_string(sig)); 26 | daemon_cleanup(); 27 | exit(EXIT_SUCCESS); 28 | } 29 | default: 30 | return FALSE; 31 | } 32 | } 33 | 34 | static HANDLE program_lock = INVALID_HANDLE_VALUE; 35 | static constexpr char program_lock_name[] = "com_puppetlabs_pxp-agent"; 36 | 37 | void daemonize() { 38 | assert(program_lock == INVALID_HANDLE_VALUE); 39 | program_lock = CreateMutexA(NULL, TRUE, program_lock_name); 40 | if (NULL == program_lock) { 41 | LOG_ERROR("Unable to acquire process lock: {1}", lth_win::system_error()); 42 | exit(EXIT_FAILURE); 43 | } else if (ERROR_ALREADY_EXISTS == GetLastError()) { 44 | LOG_ERROR("Already running daemonized"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | if (SetConsoleCtrlHandler(static_cast(ctrl_handler), TRUE)) { 49 | LOG_DEBUG("Console control handler installed"); 50 | } else { 51 | LOG_ERROR("Could not set control handler: {1}", lth_win::system_error()); 52 | daemon_cleanup(); 53 | exit(EXIT_FAILURE); 54 | } 55 | 56 | LOG_INFO("Daemonization completed; pxp-agent PID={1}, process lock '{2}'", 57 | GetCurrentProcessId(), program_lock_name); 58 | } 59 | 60 | void daemon_cleanup() { 61 | // This doesn't currently use RAII because the scope isn't clear, and global static 62 | // destruction would preclude using logging because Boost.Log also uses global static 63 | // destruction. 64 | if (program_lock != INVALID_HANDLE_VALUE && program_lock != NULL) { 65 | LOG_DEBUG("Removing process lock '{1}'", program_lock_name); 66 | ReleaseMutex(program_lock); 67 | CloseHandle(program_lock); 68 | program_lock = INVALID_HANDLE_VALUE; 69 | } 70 | } 71 | 72 | } // namespace Util 73 | } // namespace PXPAgent 74 | -------------------------------------------------------------------------------- /lib/src/util/windows/process.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.util.windows.process" 7 | #include 8 | 9 | namespace PXPAgent { 10 | namespace Util { 11 | 12 | namespace lth_win = leatherman::windows; 13 | 14 | bool processExists(int pid) { 15 | auto p_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); 16 | if (p_handle) { 17 | CloseHandle(p_handle); 18 | return true; 19 | } else { 20 | LOG_TRACE("OpenProcess failure while trying to determine the state " 21 | "of PID {1}: {2}", pid, lth_win::system_error()); 22 | } 23 | return false; 24 | } 25 | 26 | int getPid() { 27 | return GetCurrentProcessId(); 28 | } 29 | 30 | } // namespace Util 31 | } // namespace PXPAgent 32 | -------------------------------------------------------------------------------- /lib/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set include directories 2 | 3 | include_directories( 4 | inc 5 | ${Boost_INCLUDE_DIRS} 6 | ${HORSEWHISPERER_INCLUDE_DIRS} 7 | ${cpp-pcp-client_INCLUDE_DIR} 8 | ${LEATHERMAN_CATCH_INCLUDE} 9 | ) 10 | 11 | set(COMMON_TEST_SOURCES 12 | main.cc 13 | common/certs.cc 14 | common/mock_connector.cc 15 | component/external_modules_interface_test.cc 16 | unit/action_request_test.cc 17 | unit/action_response_test.cc 18 | unit/agent_test.cc 19 | unit/configuration_test.cc 20 | unit/external_module_test.cc 21 | unit/module_test.cc 22 | unit/module_cache_dir_test.cc 23 | unit/pxp_connector_v1_test.cc 24 | unit/pxp_connector_v2_test.cc 25 | unit/request_processor_test.cc 26 | unit/results_mutex_test.cc 27 | unit/results_storage_test.cc 28 | unit/thread_container_test.cc 29 | unit/time_test.cc 30 | unit/modules/command_test.cc 31 | unit/modules/ping_test.cc 32 | unit/modules/task_test.cc 33 | unit/modules/file_test.cc 34 | unit/modules/script_test.cc 35 | unit/modules/apply_test.cc 36 | unit/util/process_test.cc 37 | ) 38 | 39 | if (UNIX) 40 | set(STANDARD_TEST_SOURCES 41 | unit/util/posix/pid_file_test.cc) 42 | endif() 43 | 44 | set(test_BIN pxp-agent-unittests) 45 | 46 | add_executable(${test_BIN} ${COMMON_TEST_SOURCES} ${STANDARD_TEST_SOURCES}) 47 | target_link_libraries(${test_BIN} libpxp-agent) 48 | 49 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 50 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lpthread -pthread") 51 | endif() 52 | 53 | ADD_CUSTOM_TARGET(check 54 | "${EXECUTABLE_OUTPUT_PATH}/${test_BIN}" 55 | DEPENDS ${test_BIN} 56 | COMMENT "Executing unit tests..." 57 | VERBATIM 58 | SOURCES ${SOURCES} 59 | ) 60 | -------------------------------------------------------------------------------- /lib/tests/common/certs.cc: -------------------------------------------------------------------------------- 1 | #include "certs.hpp" 2 | 3 | #include "root_path.hpp" 4 | 5 | std::string getCaPath() { 6 | static const std::string ca { std::string { PXP_AGENT_ROOT_PATH } 7 | + "/lib/tests/resources/config/ca_crt.pem" }; 8 | return ca; 9 | } 10 | 11 | std::string getCertPath() { 12 | static const std::string cert { std::string { PXP_AGENT_ROOT_PATH } 13 | + "/lib/tests/resources/config/test_crt.pem" }; 14 | return cert; 15 | } 16 | 17 | std::string getKeyPath() { 18 | static const std::string key { std::string { PXP_AGENT_ROOT_PATH } 19 | + "/lib/tests/resources/config/test_key.pem" }; 20 | return key; 21 | } 22 | 23 | std::string getCrlPath() { 24 | static const std::string crl { std::string { PXP_AGENT_ROOT_PATH } 25 | + "/lib/tests/resources/config/test_crl.pem" }; 26 | return crl; 27 | } 28 | 29 | std::string getNotExistentFilePath() { 30 | static const std::string tmp { std::string { PXP_AGENT_ROOT_PATH } 31 | + "/lib/tests/resources/config/nothing.here" }; 32 | return tmp; 33 | } 34 | -------------------------------------------------------------------------------- /lib/tests/common/certs.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::string getCaPath(); 4 | std::string getCertPath(); 5 | std::string getKeyPath(); 6 | std::string getCrlPath(); 7 | std::string getNotACertPath(); 8 | std::string getNotExistentFilePath(); 9 | -------------------------------------------------------------------------------- /lib/tests/common/content_format.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | static boost::format ENVELOPE_FORMAT { 6 | "{ \"id\" : %1%," 7 | " \"message_type\" : %2%," 8 | " \"expires\" : %3%," 9 | " \"targets\" : [\"client_1\", \"client_2\"]," 10 | " \"sender\" : %4%," 11 | " \"destination_report\" : false" 12 | "}" 13 | }; 14 | 15 | static boost::format DATA_FORMAT { 16 | "{ \"transaction_id\" : %1%," 17 | " \"module\" : %2%," 18 | " \"action\" : %3%," 19 | " \"params\" : %4%" 20 | "}" 21 | }; 22 | 23 | static boost::format NON_BLOCKING_DATA_FORMAT { 24 | "{ \"transaction_id\" : %1%," 25 | " \"module\" : %2%," 26 | " \"action\" : %3%," 27 | " \"params\" : %4%," 28 | " \"notify_outcome\" : %5%" 29 | "}" 30 | }; 31 | 32 | static const std::string ENVELOPE_TXT { 33 | (ENVELOPE_FORMAT % "\"123456\"" 34 | % "\"test_message\"" 35 | % "\"2015-06-26T22:57:09Z\"" 36 | % "\"pcp://controller/test_controller\"").str() }; 37 | -------------------------------------------------------------------------------- /lib/tests/common/mock_connector.cc: -------------------------------------------------------------------------------- 1 | #include "mock_connector.hpp" 2 | 3 | namespace PXPAgent { 4 | 5 | MockConnector::MockConnector() 6 | : sent_provisional_response { false }, 7 | sent_non_blocking_response { false }, 8 | sent_blocking_response { false } 9 | { 10 | } 11 | 12 | void MockConnector::sendPCPError(const std::string&, 13 | const std::string&, 14 | const std::vector&) 15 | { 16 | throw MockConnector::pcpError_msg {}; 17 | } 18 | 19 | void MockConnector::sendPXPError(const ActionRequest&, 20 | const std::string&) 21 | { 22 | throw MockConnector::pxpError_msg {}; 23 | } 24 | 25 | void MockConnector::sendPXPError(const ActionResponse&) 26 | { 27 | throw MockConnector::pxpError_msg {}; 28 | } 29 | 30 | void MockConnector::sendBlockingResponse(const ActionResponse&, 31 | const ActionRequest&) 32 | { 33 | sent_blocking_response = true; 34 | } 35 | 36 | void MockConnector::sendStatusResponse(const ActionResponse& response, 37 | const ActionRequest& request) 38 | { 39 | throw MockConnector::pxpError_msg {}; 40 | } 41 | 42 | void MockConnector::sendNonBlockingResponse(const ActionResponse&) 43 | { 44 | sent_non_blocking_response = true; 45 | } 46 | 47 | void MockConnector::sendProvisionalResponse(const ActionRequest&) 48 | { 49 | sent_provisional_response = true; 50 | } 51 | 52 | void MockConnector::connect(int max_connect_attempts) 53 | { 54 | throw MockConnector::pxpError_msg {}; 55 | } 56 | 57 | void MockConnector::monitorConnection(uint32_t max_connect_attempts, 58 | uint32_t connection_check_interval_s) 59 | { 60 | throw MockConnector::pxpError_msg {}; 61 | } 62 | 63 | void MockConnector::registerMessageCallback(const PCPClient::Schema& schema, 64 | MessageCallback callback) 65 | { 66 | throw MockConnector::pxpError_msg {}; 67 | } 68 | 69 | } // namespace PXPAgent 70 | -------------------------------------------------------------------------------- /lib/tests/main.cc: -------------------------------------------------------------------------------- 1 | // Refer to https://github.com/philsquared/Catch/blob/master/docs/own-main.md 2 | // for providing our own main function to Catch 3 | #define CATCH_CONFIG_RUNNER 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | // To enable log messages: 12 | // #define ENABLE_LOGGING 13 | 14 | #ifdef ENABLE_LOGGING 15 | #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.cpp_pcp_client.test" 16 | #include 17 | #endif 18 | 19 | int main(int argc, char** argv) { 20 | #ifdef ENABLE_LOGGING 21 | leatherman::logging::setup_logging(boost::nowide::cout); 22 | leatherman::logging::set_level(leatherman::logging::log_level::debug); 23 | #endif 24 | 25 | // Create the Catch session, pass CL args, and start it 26 | Catch::Session test_session {}; 27 | 28 | // NOTE(ale): to list the reporters use: 29 | // test_session.configData().listReporters = true; 30 | 31 | // NOTE(ale): out of the box, Reporters are "xml", "junit", "console", 32 | // and "compact" (single line); "console" is the default 33 | // test_session.configData().reporterNames = 34 | // std::vector { "xml" }; 35 | 36 | // ShowDurations::Always, ::Never, ::DefaultForReporter 37 | test_session.configData().showDurations = Catch::ShowDurations::Always; 38 | 39 | // NOTE(ale): enforcing ConfigData::useColour == UseColour::No 40 | // on Windows is not necessary; the default ::Auto works fine 41 | 42 | return test_session.run(argc, argv); 43 | } 44 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/broken/exitcode: -------------------------------------------------------------------------------- 1 | asdf1 2 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/broken/metadata: -------------------------------------------------------------------------------- 1 | {"foo" : "bar"} 2 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/broken/pid: -------------------------------------------------------------------------------- 1 | y39az -------------------------------------------------------------------------------- /lib/tests/resources/action_results/broken/stderr: -------------------------------------------------------------------------------- 1 | Yeah, something went bad with this run... 2 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/broken/stdout: -------------------------------------------------------------------------------- 1 | {"report":{"kind":"unknown","time":"unknown","transaction_uuid":"unknown","environment":"unknown","status":"unknown"},"exitcode":1,"version":1,"error_type":"agent_already_running","error":"Puppet... ops missing the rest... This is bad JSON! 2 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/valid/exitcode: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/valid/metadata: -------------------------------------------------------------------------------- 1 | { 2 | "requester":"pcp://client03.example.com/pegasus-controller", 3 | "module":"pxp-module-puppet", 4 | "action":"run", 5 | "request_params":"{\"env\":[],\"flags\":[\"--no-usecacheonfailure\",\"--no-splay\",\"--show_diff\",\"--no-daemonize\",\"--onetime\",\"--verbose\"]}", 6 | "transaction_id":"1b3595ee-1b96-4ab7-a7cf-98b7f5a76b9b", 7 | "request_id":"f318e9c9-84f9-4cc6-aeb7-60ef21d700ba", 8 | "notify_outcome":false, 9 | "start":"2016-02-19T10:09:18.283484Z", 10 | "status":"success", 11 | "end":"2016-02-19T10:09:20.615601Z", 12 | "results_are_valid":true, 13 | "results":"results in a nice string format!" 14 | } 15 | -------------------------------------------------------------------------------- /lib/tests/resources/action_results/valid/pid: -------------------------------------------------------------------------------- 1 | 42 -------------------------------------------------------------------------------- /lib/tests/resources/action_results/valid/stderr: -------------------------------------------------------------------------------- 1 | Hey, all good here! -------------------------------------------------------------------------------- /lib/tests/resources/action_results/valid/stdout: -------------------------------------------------------------------------------- 1 | {"spam":"eggs"} -------------------------------------------------------------------------------- /lib/tests/resources/broken_modules/reverse_bad_json_format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | 4 | def action_metadata 5 | print 'foo' 6 | end 7 | 8 | def action_string 9 | string = $stdin.read.chomp 10 | puts string.reverse.to_json 11 | end 12 | 13 | action = ARGV.shift || 'metadata' 14 | 15 | Object.send("action_#{action}".to_sym) 16 | -------------------------------------------------------------------------------- /lib/tests/resources/broken_modules/reverse_bad_json_format.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0reverse_bad_json_format %* 2 | -------------------------------------------------------------------------------- /lib/tests/resources/broken_modules/reverse_no_metadata: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | 4 | def action_metadata 5 | end 6 | 7 | def action_string 8 | string = $stdin.read.chomp 9 | puts string.reverse.to_json 10 | end 11 | 12 | action = ARGV.shift || 'metadata' 13 | 14 | Object.send("action_#{action}".to_sym) 15 | -------------------------------------------------------------------------------- /lib/tests/resources/broken_modules/reverse_no_metadata.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0reverse_no_metadata %* 2 | -------------------------------------------------------------------------------- /lib/tests/resources/config/bad-broker.conf: -------------------------------------------------------------------------------- 1 | broker-ws-uris: ["//test_pcp_broker"] 2 | -------------------------------------------------------------------------------- /lib/tests/resources/config/bad-master.conf: -------------------------------------------------------------------------------- 1 | primary-uris: ["http://test_master"] 2 | -------------------------------------------------------------------------------- /lib/tests/resources/config/ca_crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFczCCA1ugAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhQdXBw 3 | ZXQgQ0E6IGdhenphcmEubG9jYWwwHhcNMTQwOTAyMTI1NTQ2WhcNMTkwOTAyMTI1 4 | NTQ2WjAjMSEwHwYDVQQDDBhQdXBwZXQgQ0E6IGdhenphcmEubG9jYWwwggIiMA0G 5 | CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD3IHaEaHCLN6JzRGUyfYGRoueKh+YQ 6 | h8d6o+qlJ4X7asKUgz1jqV/zRnNgehTGn82ruiFNGAtEksxnKP+EJVkaFN7DJXiX 7 | NYm2mS1H4vPy+LbQKV5dJLNU2jZisBaFbthNbIhkP+iiLAm3cENMJW23vmk/Vzqd 8 | o3hjjWgUVemOxrVyc2DuQ9oMYq27do7ZvoE4gg3m9D6CFC/ZMtOZFpaoV7E8hcUx 9 | XHclzfi8x9EBY3ZB8CyhUBL538boaoTGnlXhOQ6T4aLWntUGnf86FrvHksFdCS98 10 | BcDmyIsan4JD7zidTjZifGvqqgmmafyN3lZG5ecQX+wUSroJZ9AAX+C/TCkVucTM 11 | 5oTxBOoeLqtMUaUqwHNiC39KrMoYtfxv+a8rQN0mzP77AlZBrYOrakoc6mBKEZ3C 12 | YP8evzNrJb+i7d4JF6rcD7/2VwFW3xhETjK75Pl2Z9+vvVDD+v1pSsTXN7Um4EYJ 13 | frxF7XrwrE0in1cNZ/wHN5YcCQnvr/kWnmMrAtGkUd4s7seljriyqm9OhmB20gVj 14 | GsslEaO1jnkM5m5ma6cI9nN47bSS594/RHYz0BIyFqtNFaQZaoqPYoxPyjs2LhHe 15 | xtN3wbwW1PeBiiSh+jzxG6RHmuvECrSAW0rqRW6fv0uY9glpTXGX2KWNmfjSrR7D 16 | /WgwcO7g4grZawIDAQABo4GxMIGuMDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1Ynkv 17 | T3BlblNTTCBJbnRlcm5hbCBDZXJ0aWZpY2F0ZTAOBgNVHQ8BAf8EBAMCAQYwDwYD 18 | VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyrkYdLsKM9eoXry/CqbJwS+bJdcwNQYD 19 | VR0jBC4wLKEnpCUwIzEhMB8GA1UEAwwYUHVwcGV0IENBOiBnYXp6YXJhLmxvY2Fs 20 | ggEBMA0GCSqGSIb3DQEBCwUAA4ICAQC4fpEV6S+Kqd73Yj8y3RHwi1QrNDFyfHTT 21 | jteCXzCMRypgTLI23HHU0dU1DNfh2Jh/HJ/HXjlpt2p8RJA3VCpIgn+oNl81cFBx 22 | qgw+XRm8q5I2ht3tSXpfPTVYPZPXsfCrR6DPtznCDHdCKfDAzTAf4iCKdb0/dDxK 23 | 07mEPQn2Hfr/M03NdqWthhwWJhHcBIwr0wJOSvGarY2DyH/HlnvJzdvBxzltLmTv 24 | N7SpkSs2jFfUSLcaAfzpjNF3727tBVTp1p9B8cFvNcMO1vMo+E0muTWaosGNHP8w 25 | C0Ux+M6YlyGzgWF1pSWRvHSD2uT4veDylwQI7bybkPYgKa+rGY90hP3iTguAE2jy 26 | GQbdWAnZR/pPljRvq+CS1t+8y1mN05mHRMaqCEDLtyMl3nqepUkEizRG/+6xsXTJ 27 | 8HcLc/bsWi/mo0/pc84LKHiONNWAsJUbyPYqZQlzx5PnYzgb8uX2IQr2z037GAWZ 28 | m44KHoa0Sij21TVgPxasTUQjORUy9WeVTKw9KUZV/cmDyyWwn4m0ewnFmSfonNR8 29 | YHqsvnnGdkmMA5zWYhM1NUP1FzxVMpOf8dBjpALQqa8GoQwvt/OhZpmGo9xeMSbM 30 | 6BrsGY7bNPv7R3c1WIQpVu3sT+gUNL2q+EAo1OyhTYhWd/PfEDRyJucVe4aKk+R3 31 | 6AVYekx+NQ== 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /lib/tests/resources/config/duplicate.conf: -------------------------------------------------------------------------------- 1 | broker-ws-uri: "wss://test_pcp_broker" 2 | broker-ws-uris: ["wss://test_pcp_broker", "wss://alt_pcp_broker"] 3 | -------------------------------------------------------------------------------- /lib/tests/resources/config/empty-pxp-agent.conf: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /lib/tests/resources/config/foo.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pxp-agent/ff0f0b6b9930b14d00e93dd3cdc6720dc025bc53/lib/tests/resources/config/foo.cfg -------------------------------------------------------------------------------- /lib/tests/resources/config/multi-broker-both-uris.conf: -------------------------------------------------------------------------------- 1 | broker-ws-uris: ["wss://test_pcp_broker", "wss://alt_pcp_broker"] 2 | primary-uris: ["test_master:8140", "https://alt_master_one", "https://alt_master_two"] 3 | master-uris: ["test_master:8140", "https://alt_master_one"] 4 | -------------------------------------------------------------------------------- /lib/tests/resources/config/multi-broker-master-uris.conf: -------------------------------------------------------------------------------- 1 | broker-ws-uris: ["wss://test_pcp_broker", "wss://alt_pcp_broker"] 2 | master-uris: ["test_master:8140", "https://alt_master"] -------------------------------------------------------------------------------- /lib/tests/resources/config/multi-broker.conf: -------------------------------------------------------------------------------- 1 | broker-ws-uris: ["wss://test_pcp_broker", "wss://alt_pcp_broker"] 2 | primary-uris: ["test_master:8140", "https://alt_master"] -------------------------------------------------------------------------------- /lib/tests/resources/config/test_crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIICwTCBqgIBATANBgkqhkiG9w0BAQsFADBHMUUwQwYDVQQDDDxQdXBwZXQgRW50 3 | ZXJwcmlzZSBDQSBnZW5lcmF0ZWQgYXQgKzIwMjAtMDMtMDkgMTQ6NDM6MzIgKzAw 4 | MDAXDTIwMDMwODE0NDQzNFoXDTM1MDMwNjE0NDQ0MVqgLzAtMB8GA1UdIwQYMBaA 5 | FHTNcQZVSeucSePVGWXxHf13wTofMAoGA1UdFAQDAgEAMA0GCSqGSIb3DQEBCwUA 6 | A4ICAQA3UYxeTxHIXZ/4bGvSe7UEb0T6dYjYcShdk7zW280zduNLb1NVlHnU7y14 7 | 57nRvcH680KRBBKQGnHTeO5xTiLJdik0DZiPPHN6YUfcAzBpi60Vjy06ij3dwrm2 8 | GMuYKF5GmOz5nRhZ8e7245O4NKfoP7F3ND6MZWE0Aen8QWmaEyVAgHsMpNr6zRSo 9 | kKDmaCkjNcnsBs4kfQh4gFMDTJ2xrGW9XsbLRjprYSMo7mdeQ7Edn/bJ6bH9EMNl 10 | BwKoKJoDg7CMOM5hvJsKZqXqiVNBBrDTkLvRL2IDX53kw+ubpK47w10BV+wpHtBE 11 | +A6UUjL1VtCwAXzsk+xwKsWTGifXnyXHq44gtTlgFj/XEbNvavJTQdFGTyC21JG2 12 | IW8HPp4Jr/GBDxHsU9oHvhJaG4oszh0aYAqcTBjyVbbu69dQEi7IBMMm+tuYLOHE 13 | b8z7q158BjwvO+lG3C8H2plJo/MfpfYcczZNFrbtx6PjQzXgjBtX6PVflUT7onmJ 14 | 0kNhgjoNmUW8TnRahoPHljFwr0w0WCDQ2p95m8ATk3yLGlwH0kM+qBkpyYRnvFTa 15 | W5XBNtJ5hejrIZ42/tVAqc0zERN0+A0Mqt1TsCBuPCWnCVpgzD3QuFERqia4WjrY 16 | rawOmjMM/pARHsPTBbSPkJCvC5HjL8DF8+bBoGyBrkyqzlbR7Q== 17 | -----END X509 CRL----- 18 | -----BEGIN X509 CRL----- 19 | MIICozCBjAIBATANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDDB5QdXBwZXQgUm9v 20 | dCBDQTogZjcxMzA4NmE1MTRjZWUXDTIwMDMwODE0NDQzNFoXDTM1MDMwNjE0NDQz 21 | NlqgLzAtMB8GA1UdIwQYMBaAFFQeMsrx5+bDit+ZLo2I1oari3jdMAoGA1UdFAQD 22 | AgEAMA0GCSqGSIb3DQEBCwUAA4ICAQCrLyDh8C7QjKJIwWY7skvvJ+TlzkW374fD 23 | wLoHdrAlCcx2Tnbb7ZBTk94+AlX6s9LYa59GsvQS99uA/q1x6Q7yMEQQJqtE5OjC 24 | rKvDLqD1gEXeLbO3PGdPJhCWeF2SExIaR2b8+pt1wRIkNFVoVVb1nqur/aHnG1Mv 25 | 3ArsD44+cA17uMsBcMVVvLbZ4MaYHbbAZEh5u5v5AYCMOrpakvilylLe6S8d3zfT 26 | 50Ccv6efbNdZHQvlgy03Ogs5SEPbtAISMeEJmoCtQdPgn8xTFFQGoCJph/mG3zyp 27 | gl/kk9Y9ZRZBFeI38/r+8/18nWvFRHFjuqo4xGpTS9ZVZX05+AFaDQ+iu6hm/4/m 28 | 585TMA3wbcCufJsaFx4r+fiXnBSlLAWp2JqOVcEWZ1frbbLWu4hU+0PaMfJ7F2Pi 29 | uvGtiSipHok4SGc4SQgysAa+rxPkJrPPw3Ts225DARVKgaF8rV4ilEip/DifzCaU 30 | ris1k02PxadszMDZmRBTghzeAKCmIIkP60N8lrPRuqdt/RZXIy0rnX5s1ptDHzQ+ 31 | iPhbhlJWl4Xhcc/Q3K28/v41oHD1DhPh/X43rcT8DlLOJ+ADmI9mMlaS9Kzefo5T 32 | z85uA1YRF2jFJnzKobz1Jmi9rlNOzf438eTJDX7KRTyJO7s35WVXAeszUwjMBf4p 33 | fuUiWOoq2w== 34 | -----END X509 CRL----- 35 | -------------------------------------------------------------------------------- /lib/tests/resources/config/test_crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFcDCCA1igAwIBAgIBBDANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhQdXBw 3 | ZXQgQ0E6IGdhenphcmEubG9jYWwwHhcNMTQwOTAyMTI1NjMxWhcNMTkwOTAyMTI1 4 | NjMxWjAXMRUwEwYDVQQDDAxjdGh1bi1jbGllbnQwggIiMA0GCSqGSIb3DQEBAQUA 5 | A4ICDwAwggIKAoICAQCw+k6e4ZCFCV6p1SdThw0lZ3hj+PggqhDV8CXtoa1Vz7pS 6 | fsjm8PSrOKi0eCpYWGTdQ97gCgvv4fCp4FRn9uNjw10MwOthYA33Rh43phKwy3AY 7 | R5g1r6/GQLZSR20jAorcRFTkMOkxfsr2Xb2rTCe7JMVBZEUdrG8LxdeXjneNdiZL 8 | V+vduP2SxHAv5Bj7DigzElneRY4i3CbTx2V20FPbmubreSfsis32tU1IwonCRQBI 9 | V2ERJD8sbCQ3JC1Fvky41acY2HdkGxFQiIUhpuSBizAiLYu/fwktDV+9Xcxs4z+1 10 | 1UajP9W1c3cazKqjcGX6z9V7zN6ZjZlfiHxEoQuxlu/B+rzqUgucYXbN/6CUqcAw 11 | P+I5oqDFXn0rCWcX+d3BzUZkYofEFiyxlX/yVi8ZCJHckeE1I105yBYJyC/8MVWQ 12 | RizJBtH6kdUEVXjFD5Zje9srJ9POdJG/yZsXvBihezkWwd0LMoTQNk8HkLJxrvOw 13 | +/m25mpl9eMLSpJZpUsIiErd0QTxsaQO6pwb8qXdSM96NEFF5//aBifZSsbhFmnw 14 | V7I7IOBPr75eIFS0PDC3bLgzpE4/F/iqDHIPv0GnM9edhPnWv5DjQx4N4E7Tws+8 15 | TyoAy8H7H1je58wChb+PXzIaLttR+1qgmtgIhtXq8w7LRWoQG4tVjmeRXCCPyQID 16 | AQABo4G6MIG3MDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRl 17 | cm5hbCBDZXJ0aWZpY2F0ZTAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYI 18 | KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFM1QXtd9 19 | cTMIy3Q4dIPzweZdR3SAMB8GA1UdIwQYMBaAFMq5GHS7CjPXqF68vwqmycEvmyXX 20 | MA0GCSqGSIb3DQEBCwUAA4ICAQC30JSxQengOL3Izf6UJHd6//t/EoQJYH/75Olo 21 | sBioq2TdouFN208L9aGNjLlK9Hc4oDu8zR/XucldTTN7QJmOTqEfGde+ILdL0DpH 22 | jufAKshkWCKxyhuSWi4NKgOijdxAj/neyLu41sk8nhNTsb7KX3o5F9R9GeeK+Z8z 23 | Mp2AdAIu8Cl9nMhCJPLkZJEEHf1F0fBav1Q1LhaXTCJnAU1Zr+2I4LOJA4VXmC3f 24 | POyI3rQfSODfpK07bFHDP0fSVL8yscAs36kPoq6jVJ8l6PwaEKTYWMPhvMZBNyrJ 25 | CivVg1ctqOLj8mSDaVzuHpRoMhvEIB1tVajMxqD0sJit9H48dBULVFrmABNbGJlD 26 | 3BjzwNQgS9meW4qdH6l8K2LkwaNOjLGXuGg0Zsa26KfbilGC3fzzeIcSi6V4/Vfk 27 | 0YSEpICmBVVVQDct3kWbJeTcM1V4Liwx58MUPsra5Ub3h2QOHyRMr2J74cI+N284 28 | EyOxmkhQQQWQFfJPkqZtAPEaADPpL0dehf/GaLflocF32ToRfYLZNj8BhiMZEIKR 29 | 5ymXjcYwZwBDLUX345fOWHZkbNrHMY66Gy1By5263oNVcrCfcTLXLyFDJ/smOtnH 30 | yftfrbir/ce0HNHzBN1Xo/swiLFHeQdZh6T2z3/PLrz4bON3YyoCB6um+5OUHauU 31 | DfkeTg== 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /lib/tests/resources/config/test_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJJwIBAAKCAgEAsPpOnuGQhQleqdUnU4cNJWd4Y/j4IKoQ1fAl7aGtVc+6Un7I 3 | 5vD0qziotHgqWFhk3UPe4AoL7+HwqeBUZ/bjY8NdDMDrYWAN90YeN6YSsMtwGEeY 4 | Na+vxkC2UkdtIwKK3ERU5DDpMX7K9l29q0wnuyTFQWRFHaxvC8XXl453jXYmS1fr 5 | 3bj9ksRwL+QY+w4oMxJZ3kWOItwm08dldtBT25rm63kn7IrN9rVNSMKJwkUASFdh 6 | ESQ/LGwkNyQtRb5MuNWnGNh3ZBsRUIiFIabkgYswIi2Lv38JLQ1fvV3MbOM/tdVG 7 | oz/VtXN3Gsyqo3Bl+s/Ve8zemY2ZX4h8RKELsZbvwfq86lILnGF2zf+glKnAMD/i 8 | OaKgxV59KwlnF/ndwc1GZGKHxBYssZV/8lYvGQiR3JHhNSNdOcgWCcgv/DFVkEYs 9 | yQbR+pHVBFV4xQ+WY3vbKyfTznSRv8mbF7wYoXs5FsHdCzKE0DZPB5Cyca7zsPv5 10 | tuZqZfXjC0qSWaVLCIhK3dEE8bGkDuqcG/Kl3UjPejRBRef/2gYn2UrG4RZp8Fey 11 | OyDgT6++XiBUtDwwt2y4M6ROPxf4qgxyD79BpzPXnYT51r+Q40MeDeBO08LPvE8q 12 | AMvB+x9Y3ufMAoW/j18yGi7bUftaoJrYCIbV6vMOy0VqEBuLVY5nkVwgj8kCAwEA 13 | AQKCAgA+yD01zc0n9L/5PZ2a2xEF0OP7iyny5IHeczfxSeakx3FQrJt2mcoiJ5Jp 14 | mytiddEqUVZyYf4EFkH68ZLf4syd6oNK7/FQdPPfYad8lFSTFUhpxJQxj2aqzPAg 15 | 1ifYQKGkLDV1UgiXv2Qe6/hKGVUK5at4XCrMsQwfwAqQQEEXFO/W0JTi7io+c6Nc 16 | ye02urn2DC+HU2Nt6G15uYW8FYF6KUR8ClnoZwFT4NNO2XxnR3UnNKBBpXsntiFO 17 | 0m2hDTZIZ7rEbJ5GC/gjeprH6l2ruwXTShcGUvmdWt443lqT0jYaulM5vH6CxThe 18 | cHakTk5PIIWaSn45OOtM4a0752mu+kGWZnQFvbmlqoC7A0gGn7B/Ry8vl7rjHndH 19 | 8u2/vEMSFIhUlBjSSmbmOD3E2MbXSY084a/8AoXLHHeq/qpVYPrRm/PzGd6PD4nE 20 | R7/LjyWDVrDQCPaSSJiY8aolOjBdStTMCtsbjMf/lJMkuHTUpYScBo8QAft2aQZH 21 | U9uTblKqunLAc4RzhBfIzX4Yqzh/Lf/QpJNL3iXGTvB6LjiexYtVA8qyVs3gSK1i 22 | NoizEZyQ0iFRi6dGSfnuk7b+j4fd75g2mr+jNGczzeblOOJiG73ghbs2wDxn6Yh0 23 | 7cAFknWO5Mu1ZPx/xQtrk3bmHfuR0EoIy4H7L/wZiIqNyABC1QKCAQEA3E5jrM8K 24 | 8lpOnU8K5pTRQxt7Ailgj6hXNV/nKmjXY6OZ/yhsQCsA/9CMR6tygXiLm5shfJ59 25 | LOJXbhdvhPzHO8cmR73urt7s17g0hQIUnxkBWg6QagHiaYr20g1fHoJXCJvElgx2 26 | JfEGsBWUONEqREX5iDZWlH2iTxT8ixA9mre9d6o0P382UZeKlkajCh33TozW0l6I 27 | 6AWH6VrG99C0vlY4a3oU2W5+x4TTEJoUk+MqkIyZxX6/exGzmlQjyC/D28gdn1FX 28 | czyOTn7xFFWLhHVFMVYivWk0JTUa3FfDXCWNeH8U1K9Uam3vqJdwmKMUitlGmsj0 29 | BL6YyqhEuB663wKCAQEAzabKKK8/49WD8JeelaBk1CM/1PeXpXVgWUBBFToO1oYb 30 | btJG4b57gDsVSsqFrtDeWpSMyBs/Jy9Rc7J6FYLGBIS05+A5UT9RcenxIOv9CL2/ 31 | 5sFgQ2mcJ5+8bB4Rid7Wpsjkz7y8S1t19v5RKGePS04F58P28SzP70uU6xpNs/tk 32 | jf8B9q8Aa6xmrFNg3gkLa9ooGKw3gYSPyh7AZJIdQcGRjBgxib2mPljktGny40tJ 33 | xaD5KWyyBP/5Xh08axn55qHbzugFg3ga5GfB97Qkjgfg0U1v4j6+vctL6PTas6iu 34 | dxgJKly9aKHlZ7FdMqio7qhlvbQHxhLDV6CHzECyVwKCAQBTTlkmqYwMJNYBajhH 35 | BRM0exnCsX7QE+oWRtOVF9wK03ySHekBQsG3+lZxa4V7K+guyTSaeRwAcv54pOgI 36 | s8Vq912kV1AAVE/fKsrBWQ4PM1dthIBbdab2HhRDTccgNE1Q9gYab0/l/QR4ZVCN 37 | Q5rmAkffmByZPgC4QslUlKmW7c/5hiUHtcUGiXVgagmQ88/mph89oX3Hrv+74BXI 38 | 8+TE7Lmc+qcTqVLLg5CEgJ8zZP1dd61WHx2tHf56Z0K4mkdHvp5k9k9JRibRy/LB 39 | q698QRQPGfE9CqErSK+FjrJTD+g1Hmj0G/Ch5jxv1gQ/07sa3hpywXZ0rNX+HC8Q 40 | KJ6lAoIBABlmBgWsTzm2PwtRznYwTwOcLuAAjCwVKQcc1mzmtLpt6alwMuzid6JI 41 | qcKI7KXAihWpzxFPvCb8wxSE8GsDsa0RLr6MhMeyCD8HY45rVt7jgHXhYCAzROPn 42 | 6H953zSl3uDOYuP9LUwpSZJtqlxRHfNw+W28WB1FKZEmRogawYn+/FZFLlrnFig2 43 | LKAP1CDYAR8a3GTfsCNA2flefuhs20wpue4RdtzKHWQJ2oUlqKfFvqC31s307K+N 44 | ZaDn/3RCywhe6STOpw+rn7ah0eTjzLf15SA0biMFGM2b9A4bX39cz99Jiqg+t+3a 45 | QNjjoN0G04ZWKeqZ93PREWsbGmpxexUCggEAej/wi7tl2YonApuyVWtbCDBWnJI6 46 | HNRKPSXvPzKh1XO6B+eBN9WHAI6QCfK6RjxrGQxajPXO0yVu+05/wDiV+L7riqtu 47 | lzFFKzRTseXJ4NgPiDUF79xzIwu3QP2+yuRnADt/i1GvSA/taVzraqdSbdeYJYcf 48 | 94gY7/ZBsNQWEvwTgok9THkOqwLMwTelX7sYmDxyhi8r5yia18vPR66rHH3cD9+E 49 | xgWcAKOGOIMT1lmeVQHTmIBtT6jpvK3o9SIBdHZwilt5xy+AEqsISo6bDa9IWdn0 50 | rLc8Ll9F8RvyJWxtf7dD/b8rtxnp+AY18MHv7sHlQp2pEG/SkkrRcYtz+w== 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /lib/tests/resources/config/unknown.conf: -------------------------------------------------------------------------------- 1 | some-new-option: true 2 | broker-ws-uri: "wss://test_pcp_broker" 3 | -------------------------------------------------------------------------------- /lib/tests/resources/download_files/file.txt: -------------------------------------------------------------------------------- 1 | some fake text 2 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/check_output.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | def check_output_files(args) 4 | output_files = args["output_files"] 5 | if !output_files 6 | return 7 | end 8 | 9 | $stdout.reopen(File.open(output_files["stdout"], 'w')) 10 | $stderr.reopen(File.open(output_files["stderr"], 'w')) 11 | 12 | at_exit do 13 | status = if $!.nil? 14 | 0 15 | elsif $!.is_a?(SystemExit) 16 | $!.status 17 | else 18 | 1 19 | end 20 | $stdout.fsync 21 | $stderr.fsync 22 | File.open(output_files["exitcode"], 'w') do |f| 23 | f.puts(status) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/convert_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | require_relative './check_output' 4 | 5 | def action_metadata 6 | metadata = { 7 | :description => "currency conversions", 8 | :configuration => { 9 | :type => "object", 10 | :properties => { 11 | :rate => { 12 | :type => "number" 13 | }, 14 | :fee_percent => { 15 | :type => "number" 16 | }, 17 | :fee_max => { 18 | :type => "number" 19 | } 20 | }, 21 | :required => [:rate], 22 | :additionalProperties => false 23 | }, 24 | :actions => [ 25 | { 26 | :name => "convert", 27 | :description => "converts a foreign currency using given rate and applying a fee", 28 | :input => { 29 | :type => "object", 30 | :properties => { 31 | :amount => { 32 | :type => "number" 33 | }, 34 | }, 35 | :required => [ :amount ] 36 | }, 37 | :results => { 38 | :type => "object", 39 | :properties => { 40 | :amount => { 41 | :type => "number" 42 | }, 43 | }, 44 | :required => [ :amount ] 45 | } 46 | }, 47 | { 48 | :name => "convert2", 49 | :description => "converts a foreign currency using given rate and applying a fee", 50 | :input => { 51 | :type => "object", 52 | :properties => { 53 | :amount => { 54 | :type => "number" 55 | }, 56 | }, 57 | :required => [ :amount ] 58 | }, 59 | :results => { 60 | :type => "object", 61 | :properties => { 62 | :amount => { 63 | :type => "number" 64 | }, 65 | }, 66 | :required => [ :amount ] 67 | } 68 | } 69 | ] 70 | } 71 | 72 | puts metadata.to_json 73 | end 74 | 75 | def do_convert 76 | args = JSON.load($stdin) 77 | check_output_files(args) 78 | config = args['configuration'] 79 | input = args['input'] 80 | 81 | amount = input['amount'] * config['rate'] 82 | fee_percent = config['fee_percent'] 83 | fee = fee_percent.nil? ? 0 : amount * fee_percent / 100 84 | fee_max = config['fee_max'] 85 | if !fee_max.nil? && fee > fee_max 86 | fee = fee_max 87 | end 88 | 89 | amount - fee 90 | end 91 | 92 | def action_convert 93 | results = { :amount => do_convert } 94 | puts results.to_json 95 | end 96 | 97 | def action_convert2 98 | results = { :amount2 => do_convert } # intentionally doesn't match schema 99 | puts results.to_json 100 | end 101 | 102 | action = ARGV.shift || 'metadata' 103 | 104 | Object.send("action_#{action}".to_sym) 105 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/convert_test.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0convert_test %* 2 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/failures_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | require 'json' 4 | require_relative './check_output' 5 | 6 | def action_metadata 7 | metadata = { 8 | :description => "schema for a test module for testing failures", 9 | :actions => [ 10 | { :name => "get_an_invalid_result", 11 | :description => "sure this fails", 12 | :input => { 13 | :type => "object", 14 | :properties => { 15 | :argument => { 16 | :type => "string", 17 | }, 18 | }, 19 | :required => [ :argument ], 20 | }, 21 | :results => { 22 | :type => "object", 23 | :properties => { 24 | :output => { 25 | :type => "object", 26 | }, 27 | }, 28 | :required => [ :output ], 29 | }, 30 | }, 31 | { :name => "broken_action", 32 | :description => "sure this fails", 33 | :input => { 34 | :type => "object", 35 | :properties => { 36 | :argument => { 37 | :type => "string", 38 | }, 39 | }, 40 | :required => [ :argument ], 41 | }, 42 | :results => { 43 | :type => "object", 44 | :properties => { 45 | :output => { 46 | :type => "string", 47 | }, 48 | }, 49 | :required => [ :output ], 50 | }, 51 | }, 52 | ], 53 | } 54 | 55 | puts metadata.to_json 56 | end 57 | 58 | def action_get_an_invalid_result 59 | args = JSON.load($stdin) 60 | check_output_files(args) 61 | puts "not valid JSON object - it will not be parsed successfully!" 62 | end 63 | 64 | def action_broken_action 65 | args = JSON.load($stdin) 66 | check_output_files(args) 67 | $stderr.puts "we failed, sorry ☹" 68 | raise "ops, we failed!" 69 | end 70 | 71 | action = ARGV.shift || 'metadata' 72 | 73 | Object.send("action_#{action}".to_sym) 74 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/failures_test.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0failures_test %* -------------------------------------------------------------------------------- /lib/tests/resources/modules/reverse_valid: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | require_relative './check_output' 4 | 5 | def action_metadata 6 | metadata = { 7 | :description => "schema test", 8 | :configuration => { 9 | :type => "object", 10 | :properties => { 11 | :spam_dir => { 12 | :type => "string" 13 | }, 14 | :eggs_dir => { 15 | :type => "string" 16 | }, 17 | :beans_file => { 18 | :type => "string" 19 | } 20 | }, 21 | :required => [:spam_dir, :eggs_dir, :beans_file], 22 | :additionalProperties => false, 23 | }, 24 | :actions => [ 25 | { :name => "string", 26 | :description => "reverses a string", 27 | :input => { 28 | :type => "object", 29 | :properties => { 30 | :argument => { 31 | :type => "string", 32 | }, 33 | }, 34 | :required => [ :argument ], 35 | }, 36 | :results => { 37 | :type => "object", 38 | :properties => { 39 | :output => { 40 | :type => "string", 41 | }, 42 | }, 43 | :required => [ :output ], 44 | }, 45 | }, 46 | { :name => "hash", 47 | :description => "reverses an element of a hash", 48 | :input => { 49 | :type => "object", 50 | :properties => { 51 | :the_input => { 52 | :type => 'string', 53 | }, 54 | }, 55 | :required => [ :input ], 56 | }, 57 | :results => { 58 | :type => "object", 59 | :properties => { 60 | :the_input => { 61 | :type => 'string', 62 | }, 63 | :the_output => { 64 | :type => 'string', 65 | }, 66 | }, 67 | :required => [ :input, :output ], 68 | }, 69 | }, 70 | ], 71 | } 72 | 73 | puts metadata.to_json 74 | end 75 | 76 | def action_string 77 | args = JSON.load($stdin) 78 | check_output_files(args) 79 | input_args = args['input'] 80 | results = { :output => input_args['argument'].reverse } 81 | puts results.to_json 82 | end 83 | 84 | def action_hash 85 | args = JSON.load($stdin) 86 | check_output_files(args) 87 | input_args = args['input'] 88 | input_args['the_output'] = input_args['the_input'].reverse 89 | puts input_args.to_json 90 | end 91 | 92 | action = ARGV.shift || 'metadata' 93 | 94 | Object.send("action_#{action}".to_sym) 95 | -------------------------------------------------------------------------------- /lib/tests/resources/modules/reverse_valid.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0reverse_valid %* -------------------------------------------------------------------------------- /lib/tests/resources/modules_broken/reverse_broken: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | 4 | def action_metadata 5 | metadata = { 6 | :description => "broken reverse schema 01", 7 | :wrong_key_here_expected_actions => [ # <-- error here 8 | { :name => "string", 9 | :description => "reverses a string", 10 | :input => { 11 | :type => "string", 12 | }, 13 | :results => { 14 | :type => "string", 15 | }, 16 | }, 17 | ], 18 | } 19 | 20 | puts metadata.to_json 21 | end 22 | 23 | def action_string 24 | string = $stdin.read.chomp 25 | puts string.reverse.to_json 26 | end 27 | 28 | action = ARGV.shift || 'metadata' 29 | 30 | Object.send("action_#{action}".to_sym) 31 | -------------------------------------------------------------------------------- /lib/tests/resources/modules_broken/reverse_broken.bat: -------------------------------------------------------------------------------- 1 | @ruby.exe %~dp0reverse_broken %* -------------------------------------------------------------------------------- /lib/tests/resources/modules_config_bad_format/reverse_valid.conf: -------------------------------------------------------------------------------- 1 | { 2 | "this is bad JSON!!!" : true 3 | ] 4 | ] 5 | -------------------------------------------------------------------------------- /lib/tests/resources/modules_config_broken/reverse_valid.conf: -------------------------------------------------------------------------------- 1 | { 2 | "this object is in a valid JSON format" : true, 3 | "but it does not comply with the JSON schema" : true, 4 | "provided by the module's metadata" : true 5 | } 6 | -------------------------------------------------------------------------------- /lib/tests/resources/modules_config_valid/reverse_valid.conf: -------------------------------------------------------------------------------- 1 | { 2 | "spam_dir" : "/tmp/unused_path_value/for_an_expected_config_entry", 3 | "eggs_dir" : "/tmp/another_one", 4 | "beans_file" : "/tmp/the_last_one" 5 | } 6 | -------------------------------------------------------------------------------- /lib/tests/resources/purge_test/valid_old/metadata: -------------------------------------------------------------------------------- 1 | { 2 | "requester":"pcp://client03.example.com/pegasus-controller", 3 | "module":"pxp-module-puppet", 4 | "action":"run", 5 | "request_params":"{\"env\":[],\"flags\":[\"--no-usecacheonfailure\",\"--no-splay\",\"--show_diff\",\"--no-daemonize\",\"--onetime\",\"--verbose\"]}", 6 | "transaction_id":"1b3595ee-1b96-4ab7-a7cf-98b7f5a76b9b", 7 | "request_id":"f318e9c9-84f9-4cc6-aeb7-60ef21d700ba", 8 | "notify_outcome":false, 9 | "start":"2016-01-11T10:09:18.283484Z", 10 | "status":"success", 11 | "end":"2016-01-11T10:09:28.1234Z", 12 | "results_are_valid":true, 13 | "results":"results in a nice string format!" 14 | } 15 | -------------------------------------------------------------------------------- /lib/tests/resources/purge_test/valid_recent/metadata: -------------------------------------------------------------------------------- 1 | {"requester":"pcp://client03.example.com/pegasus-controller","module":"pxp-module-puppet","action":"run","request_params":"{}","transaction_id":"1b3595ee-1b96-4ab7-a7cf-98b7f5a76b9b","request_id":"f318e9c9-84f9-4cc6-aeb7-60ef21d700ba","notify_outcome":false,"start":"2016-02-23T10:10:46.663944Z","status":"success","end":"2016-02-23T10:10:46.663955Z","results_are_valid":true,"results":"results in a nice string format!"} 2 | -------------------------------------------------------------------------------- /lib/tests/resources/scripts_cache/3D57FCC5FBDC53E667D16493D38621F36B2CF7DB244986B07A4FBCFECE9E19BD/test script.ps1: -------------------------------------------------------------------------------- 1 | if ($Args.count -eq 0) { 2 | Write-Output "TESTING" 3 | exit 4 | } elseif ($Args[0] -eq "FAIL") { 5 | Write-Error "FAIL TESTING" 6 | exit 5 7 | } else { 8 | $str="" 9 | foreach ($arg in $Args) { 10 | $str += "| $arg " 11 | } 12 | Write-Output $str 13 | } 14 | -------------------------------------------------------------------------------- /lib/tests/resources/scripts_cache/3D57FCC5FBDC53E667D16493D38621F36B2CF7DB244986B07A4FBCFECE9E19BD/test_script.ps1: -------------------------------------------------------------------------------- 1 | if ($Args.count -eq 0) { 2 | Write-Output "TESTING" 3 | exit 4 | } elseif ($Args[0] -eq "FAIL") { 5 | Write-Error "FAIL TESTING" 6 | exit 5 7 | } else { 8 | $str="" 9 | foreach ($arg in $Args) { 10 | $str += "| $arg " 11 | } 12 | Write-Output $str 13 | } 14 | -------------------------------------------------------------------------------- /lib/tests/resources/scripts_cache/71A2725F36221AAE75AF076D01D18744DA461A02DD2AB746447BC41C71248562/test_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z $1 ]; then 4 | echo 'TESTING' 5 | elif [ "$1" = "FAIL" ]; then 6 | echo 'FAIL TESTING' >&2 7 | exit 5 8 | else 9 | str="" 10 | for VAR in "$@" 11 | do 12 | str="$str | $VAR" 13 | done 14 | echo $str 15 | fi 16 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/0d75633b5dd4b153496b4593e9d94e69265d2a812579f724ba0b4422b0bfb836/unparseable.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pxp-agent/ff0f0b6b9930b14d00e93dd3cdc6720dc025bc53/lib/tests/resources/tasks-cache/0d75633b5dd4b153496b4593e9d94e69265d2a812579f724ba0b4422b0bfb836/unparseable.bat -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/15f26bdeea9186293d256db95fed616a7b823de947f4e9bd0d8d23c5ac786d13/init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cat - 3 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/1c616ed98f54880444d0c49036cdf930120457c20e7a9a204db750f2d6162999/printer.bat: -------------------------------------------------------------------------------- 1 | @echo %PT_message% 2 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/28dd63928f9e3837e11e36f5af35c09068e4d62b355cf169a873a0cdf30f7c95/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat "$PT__installdir/file1.txt" 4 | cat "$PT__installdir/dir/file2.txt" 5 | cat "$PT__installdir/dir/sub_dir/file3.txt" 6 | cat "$PT__installdir/test/tasks/init.sh" 7 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/554f86a33add88c371c2bbb79839c9adfd3d420dc5f405a07e97fab54efbe1ba/error.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo hello 3 | echo goodbye 1>&2 4 | exit 1 5 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/67ee5478eaadb034ba59944eb977797b49ca6aa8d3574587f36ebcbeeb65f70e/file2.txt: -------------------------------------------------------------------------------- 1 | file2 2 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/823c013467ce03b12dbe005757a6c842894373e8bcfb0cf879329afb5abcd543/multi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $PT_message 3 | cat - 4 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/88a07e5b672aa44a91aa7d63e22c91510af5d4707e12f75e0d5de2dfdbde1dec/multi.bat: -------------------------------------------------------------------------------- 1 | @echo %PT_message% 2 | @more 3 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/936e85a9b7f1e7b4b593c9f051a36105ed36f7fb8dcff67ff23a3a9af2abe962/printer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $PT_message 3 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/94f6e58bd04a4513b8301e75f40527cf7610c66d1960b26f6ac2e743e108bdac/file3.txt: -------------------------------------------------------------------------------- 1 | file3 2 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/b26e34bc50c88ca5ee2bfcbcaff5c23b0124db9479e66390539f2715b675b7e7/null_byte: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | printf "foo\0bar" 4 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/d2795e0a1b66ca75be9e2be25c2a61fdbab9efc641f8e480f5ab1b348112701d/unparseable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pxp-agent/ff0f0b6b9930b14d00e93dd3cdc6720dc025bc53/lib/tests/resources/tasks-cache/d2795e0a1b66ca75be9e2be25c2a61fdbab9efc641f8e480f5ab1b348112701d/unparseable -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/d5b8819b51ecd53b32de74c09def0e71f617076bc8e4f75e1eac99b8f77a6c70/error: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo hello 3 | echo goodbye 1>&2 4 | exit 1 5 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/e1c10f8c709f06f4327ac6a07a918e297a039a24a788fabf4e2ebc31d16e8dc3/init.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | more 3 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/ea7d652bfe797121a7ac8e3654aacf7d50a9a4665e843669b873995b072d820b/init.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set installdir=%PT__installdir:/=\% 3 | type %installdir%\file1.txt %installdir%\dir\file2.txt %installdir%\dir\sub_dir\file3.txt %installdir%\test\tasks\init.bat 2>nul 4 | -------------------------------------------------------------------------------- /lib/tests/resources/tasks-cache/ecdc5536f73bdae8816f0ea40726ef5e9b810d914493075903bb90623d97b1d8/file1.txt: -------------------------------------------------------------------------------- 1 | file1 2 | -------------------------------------------------------------------------------- /lib/tests/unit/module_test.cc: -------------------------------------------------------------------------------- 1 | #include "../common/content_format.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include // ParsedChunks 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | using namespace PXPAgent; 16 | 17 | namespace lth_jc = leatherman::json_container; 18 | 19 | static const std::string ECHO_ACTION { "echo" }; 20 | static const std::string FAKE_ACTION { "FAKE_ACTION" }; 21 | 22 | static const std::vector NO_DEBUG {}; 23 | 24 | static const std::string ECHO_TXT { 25 | (DATA_FORMAT % "\"0987\"" 26 | % "\"echo\"" 27 | % "\"echo\"" 28 | % "{ \"argument\" : \"maradona\" }").str() }; 29 | 30 | static const PCPClient::ParsedChunks PARSED_CHUNKS { 31 | lth_jc::JsonContainer(ENVELOPE_TXT), 32 | lth_jc::JsonContainer(ECHO_TXT), 33 | NO_DEBUG, 34 | 0 }; 35 | 36 | TEST_CASE("Module::type", "[modules]") { 37 | Modules::Echo echo_module {}; 38 | 39 | SECTION("correctly reports its type") { 40 | REQUIRE(echo_module.type() == ModuleType::Internal); 41 | } 42 | } 43 | 44 | TEST_CASE("Module::hasAction", "[modules]") { 45 | Modules::Echo echo_module {}; 46 | 47 | SECTION("correctly reports false") { 48 | REQUIRE(!echo_module.hasAction("foo")); 49 | } 50 | 51 | SECTION("correctly reports true") { 52 | REQUIRE(echo_module.hasAction(ECHO_ACTION)); 53 | } 54 | } 55 | 56 | TEST_CASE("Module::executeAction", "[modules]") { 57 | Modules::Echo echo_module {}; 58 | 59 | SECTION("it should correctly call echo") { 60 | ActionRequest request { RequestType::Blocking, PARSED_CHUNKS }; 61 | auto response = echo_module.executeAction(request); 62 | auto txt = response.action_metadata.get({ "results", "outcome" }); 63 | REQUIRE(txt == "maradona"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/tests/unit/pxp_connector_v1_test.cc: -------------------------------------------------------------------------------- 1 | #include "../common/mock_connector.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace PXPAgent; 8 | 9 | TEST_CASE("PXPConnectorV1::PXPConnectorV1", "[agent]") { 10 | SECTION("successfully instantiates with valid arguments") { 11 | REQUIRE_NOTHROW((PXPConnectorV1{AGENT_CONFIGURATION, nullptr})); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /lib/tests/unit/pxp_connector_v2_test.cc: -------------------------------------------------------------------------------- 1 | #include "../common/mock_connector.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace PXPAgent; 8 | 9 | TEST_CASE("PXPConnectorV2::PXPConnectorV2", "[agent]") { 10 | SECTION("successfully instantiates with valid arguments") { 11 | REQUIRE_NOTHROW((PXPConnectorV2{AGENT_CONFIGURATION, nullptr})); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /lib/tests/unit/results_mutex_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace PXPAgent; 8 | 9 | TEST_CASE("ResultsMutex::exists", "[async]") { 10 | ResultsMutex::Instance().reset(); 11 | 12 | SECTION("returns false correctly") { 13 | ResultsMutex::LockGuard lck { ResultsMutex::Instance().access_mtx }; 14 | REQUIRE_FALSE(ResultsMutex::Instance().exists("spam")); 15 | } 16 | 17 | SECTION("returns true correctly") { 18 | ResultsMutex::Instance().add("beans"); 19 | ResultsMutex::LockGuard lck { ResultsMutex::Instance().access_mtx }; 20 | REQUIRE(ResultsMutex::Instance().exists("beans")); 21 | } 22 | } 23 | 24 | TEST_CASE("ResultsMutex::get", "[async]") { 25 | ResultsMutex::Instance().reset(); 26 | 27 | SECTION("can lock with the returned mutex pointer") { 28 | ResultsMutex::Instance().add("spam"); 29 | ResultsMutex::Mutex_Ptr mtx_ptr; 30 | ResultsMutex::Lock lck { ResultsMutex::Instance().access_mtx, 31 | PCPClient::Util::defer_lock }; 32 | REQUIRE_FALSE(lck.owns_lock()); 33 | lck.lock(); 34 | REQUIRE(lck.owns_lock()); 35 | mtx_ptr = ResultsMutex::Instance().get("spam"); 36 | lck.unlock(); 37 | REQUIRE_FALSE(lck.owns_lock()); 38 | REQUIRE_NOTHROW(ResultsMutex::LockGuard(*mtx_ptr)); 39 | } 40 | 41 | SECTION("throws an error if the named mutex doesn't exist") { 42 | ResultsMutex::LockGuard lck { ResultsMutex::Instance().access_mtx }; 43 | REQUIRE_FALSE(ResultsMutex::Instance().exists("eggs")); 44 | REQUIRE_THROWS_AS(ResultsMutex::Instance().get("eggs"), 45 | ResultsMutex::Error); 46 | } 47 | } 48 | 49 | TEST_CASE("ResultsMutex::add", "[async]") { 50 | ResultsMutex::Instance().reset(); 51 | 52 | SECTION("can add") { 53 | REQUIRE_NOTHROW(ResultsMutex::Instance().add("foo")); 54 | REQUIRE(ResultsMutex::Instance().exists("foo")); 55 | } 56 | 57 | SECTION("throws an error when adding duplicates") { 58 | ResultsMutex::Instance().add("bar"); 59 | REQUIRE_THROWS_AS(ResultsMutex::Instance().add("bar"), 60 | ResultsMutex::Error); 61 | } 62 | } 63 | 64 | TEST_CASE("ResultsMutex::remove", "[async]") { 65 | ResultsMutex::Instance().reset(); 66 | 67 | SECTION("can remove") { 68 | ResultsMutex::Instance().add("spam"); 69 | REQUIRE(ResultsMutex::Instance().exists("spam")); 70 | REQUIRE_NOTHROW(ResultsMutex::Instance().remove("spam")); 71 | REQUIRE_FALSE(ResultsMutex::Instance().exists("spam")); 72 | 73 | REQUIRE_NOTHROW(ResultsMutex::Instance().add("foo")); 74 | } 75 | 76 | SECTION("throws an error if the named mutex doesn't exist") { 77 | REQUIRE_FALSE(ResultsMutex::Instance().exists("eggs")); 78 | REQUIRE_THROWS_AS(ResultsMutex::Instance().remove("eggs"), 79 | ResultsMutex::Error); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/tests/unit/util/process_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #ifdef _WIN32 6 | #include 7 | #undef ERROR 8 | #else 9 | #include 10 | #endif 11 | 12 | using namespace PXPAgent; 13 | using namespace Util; 14 | 15 | TEST_CASE("processExists", "[util]") { 16 | SECTION("this process is executing") { 17 | #ifdef _WIN32 18 | REQUIRE(processExists(GetCurrentProcessId())); 19 | #else 20 | REQUIRE(processExists(getpid())); 21 | #endif 22 | } 23 | } 24 | 25 | TEST_CASE("getPid", "[util]") { 26 | SECTION("can call it") { 27 | REQUIRE_NOTHROW(getPid()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /modules/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'puppet', '~> 4.2' 4 | 5 | group :test do 6 | gem 'rspec', '~> 3.1' 7 | end 8 | 9 | if File.exists? "#{__FILE__}.local" 10 | eval(File.read("#{__FILE__}.local"), binding) 11 | end 12 | -------------------------------------------------------------------------------- /modules/pxp-module-puppet.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL 3 | 4 | call "%~dp0..\..\bin\environment.bat" %0 %* 5 | 6 | ruby -S -- "%~dp0\pxp-module-puppet" %* 7 | -------------------------------------------------------------------------------- /templates/root_path.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TEMPLATES_PXP_AGENT_ROOT_PATH_HPP_ 2 | #define TEMPLATES_PXP_AGENT_ROOT_PATH_HPP_ 3 | 4 | #define PXP_AGENT_ROOT_PATH "@ROOT_PATH@" 5 | #define PXP_AGENT_BIN_PATH "@EXECUTABLE_OUTPUT_PATH@" 6 | 7 | #endif // TEMPLATES_PXP_AGENT_ROOT_PATH_HPP_ 8 | -------------------------------------------------------------------------------- /templates/version-inl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TEMPLATES_AGENT_VERSION_INL_HPP_ 2 | #define TEMPLATES_AGENT_VERSION_INL_HPP_ 3 | 4 | #define PXP_AGENT_VERSION "@PROJECT_VERSION@" 5 | 6 | #endif // TEMPLATES_AGENT_VERSION_INL_HPP_ 7 | -------------------------------------------------------------------------------- /vendor/FindDependency.cmake: -------------------------------------------------------------------------------- 1 | # A function for finding dependencies 2 | function(find_dependency) 3 | include(CMakeParseArguments) 4 | cmake_parse_arguments(FIND_DEPENDENCY "" "DISPLAY" "HEADERS;LIBRARIES" ${ARGN}) 5 | 6 | set(FIND_DEPENDENCY_NAME ${ARGV0}) 7 | 8 | # Setup the include path hint 9 | if (${FIND_DEPENDENCY_NAME}_INCLUDEDIR) 10 | set(INCLUDE_HINTS "${${FIND_DEPENDENCY_NAME}_INCLUDEDIR}") 11 | elseif (${FIND_DEPENDENCY_NAME}_ROOT) 12 | set(INCLUDE_HINTS "${${FIND_DEPENDENCY_NAME}_ROOT}/include" "${${FIND_DEPENDENCY_NAME}_ROOT}") 13 | endif() 14 | 15 | # Setup the library path hint 16 | if (${FIND_DEPENDENCY_NAME}_LIBRARYDIR) 17 | set(LIBRARY_HINTS "${${FIND_DEPENDENCY_NAME}_LIBRARYDIR}") 18 | elseif (${FIND_DEPENDENCY_NAME}_ROOT) 19 | set(LIBRARY_HINTS "${${FIND_DEPENDENCY_NAME}_ROOT}") 20 | endif() 21 | 22 | # Find headers and libraries 23 | find_path(${FIND_DEPENDENCY_NAME}_INCLUDE_DIR NAMES ${FIND_DEPENDENCY_HEADERS} HINTS ${INCLUDE_HINTS} PATH_SUFFIXES include) 24 | find_library(${FIND_DEPENDENCY_NAME}_LIBRARY NAMES ${FIND_DEPENDENCY_LIBRARIES} HINTS ${LIBRARY_HINTS} PATH_SUFFIXES bin lib) 25 | 26 | # Handle the find_package arguments 27 | include(FindPackageHandleStandardArgs) 28 | find_package_handle_standard_args(${FIND_DEPENDENCY_NAME} "${FIND_DEPENDENCY_DISPLAY} was not found." ${FIND_DEPENDENCY_NAME}_LIBRARY ${FIND_DEPENDENCY_NAME}_INCLUDE_DIR) 29 | 30 | # Set the output variables in the parent's scope 31 | if (${FIND_DEPENDENCY_NAME}_FOUND) 32 | set(${FIND_DEPENDENCY_NAME}_FOUND ${${FIND_DEPENDENCY_NAME}_FOUND} PARENT_SCOPE) 33 | 34 | # Include dirs 35 | set(${FIND_DEPENDENCY_NAME}_INCLUDE_DIRS ${${FIND_DEPENDENCY_NAME}_INCLUDE_DIR} PARENT_SCOPE) 36 | 37 | # Libraries 38 | if (${FIND_DEPENDENCY_NAME}_LIBRARY) 39 | set(${FIND_DEPENDENCY_NAME}_LIBRARIES ${${FIND_DEPENDENCY_NAME}_LIBRARY} PARENT_SCOPE) 40 | else() 41 | set(${FIND_DEPENDENCY_NAME}_LIBRARIES "" PARENT_SCOPE) 42 | endif() 43 | 44 | # Get the library name 45 | get_filename_component(${FIND_DEPENDENCY_NAME}_LIBRARY_DIRS ${${FIND_DEPENDENCY_NAME}_LIBRARY} PATH) 46 | set(${FIND_DEPENDENCY_NAME_LIBRARY} ${${FIND_DEPENDENCY_NAME_LIBRARY}} PARENT_SCOPE) 47 | 48 | # Add a define for the found package; ensure it's a valid C identifier 49 | string(MAKE_C_IDENTIFIER ${FIND_DEPENDENCY_NAME} DEPENDENCY_NAME_ID) 50 | add_definitions(-DUSE_${DEPENDENCY_NAME_ID}) 51 | endif() 52 | 53 | # Advanced options for not cluttering the cmake UIs 54 | mark_as_advanced(${FIND_DEPENDENCY_NAME}_INCLUDE_DIR ${FIND_DEPENDENCY_NAME}_LIBRARY) 55 | endfunction() 56 | -------------------------------------------------------------------------------- /vendor/horsewhisperer-0.13.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/pxp-agent/ff0f0b6b9930b14d00e93dd3cdc6720dc025bc53/vendor/horsewhisperer-0.13.1.zip -------------------------------------------------------------------------------- /vendor/horsewhisperer.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | 3 | # Add an external project to build horsewhisperer 4 | externalproject_add( 5 | horsewhisperer 6 | PREFIX "${PROJECT_BINARY_DIR}" 7 | URL "file://${VENDOR_DIRECTORY}/horsewhisperer-0.13.1.zip" 8 | URL_MD5 "9bd0fb56c684685deb1b2a00f5148c79" 9 | CONFIGURE_COMMAND "" 10 | BUILD_COMMAND "" 11 | BUILD_IN_SOURCE 1 12 | INSTALL_COMMAND "" 13 | ) 14 | externalproject_get_property(horsewhisperer SOURCE_DIR) 15 | set(HORSEWHISPERER_INCLUDE_DIRS "${SOURCE_DIR}/include") 16 | --------------------------------------------------------------------------------