diff --git a/acceptance/lib/puppet_x/acceptance/external_cert_fixtures.rb b/acceptance/lib/puppet_x/acceptance/external_cert_fixtures.rb
index 7eadc7426..5a8a0610a 100644
--- a/acceptance/lib/puppet_x/acceptance/external_cert_fixtures.rb
+++ b/acceptance/lib/puppet_x/acceptance/external_cert_fixtures.rb
@@ -1,324 +1,361 @@
module PuppetX
module Acceptance
class ExternalCertFixtures
attr_reader :fixture_dir
attr_reader :test_dir
attr_reader :master_name
attr_reader :agent_name
##
# ExternalCerts provides a utility class to fill in fixture data and other
# large blobs of text configuration for the acceptance testing of External CA
# behavior.
#
# @param [String] fixture_dir The fixture directory to read from.
#
# @param [String] test_dir The directory on the remote system, used for
# filling in templates.
#
# @param [String] master_name The common name the master should be reachable
# at. This name should match up with the certificate files in the fixture
# directory, e.g. master1.example.org.
#
# @param [String] agent_name The common name the agent is configured to use.
# This name should match up with the certificate files in the fixture
# directory, e.g.
def initialize(fixture_dir, test_dir, master_name = "master1.example.org", agent_name = "agent1.example.org")
@fixture_dir = fixture_dir
@test_dir = test_dir
@master_name = master_name
@agent_name = agent_name
end
def master_short_name
@master_short_name ||= master_name.gsub(/\..*/, '')
end
def host_entry
- @host_entry ||= "127.0.0.3 #{master_name} #{master_short_name} puppet\n"
+ @host_entry ||= "127.0.0.3 #{master_name} #{master_short_name} puppet"
end
def root_ca_cert
@root_ca_cert ||= File.read(File.join(fixture_dir, 'root', 'ca-root.crt'))
end
def agent_ca_cert
@agent_ca_cert ||= File.read(File.join(fixture_dir, 'agent-ca', 'ca-agent-ca.crt'))
end
def master_ca_cert
@master_ca_cert ||= File.read(File.join(fixture_dir, 'master-ca', 'ca-master-ca.crt'))
end
def master_ca_crl
@master_ca_crl ||= File.read(File.join(fixture_dir, 'master-ca', 'ca-master-ca.crl'))
end
def agent_cert
@agent_cert ||= File.read(File.join(fixture_dir, 'leaves', "#{agent_name}.issued_by.agent-ca.crt"))
end
def agent_key
@agent_key ||= File.read(File.join(fixture_dir, 'leaves', "#{agent_name}.issued_by.agent-ca.key"))
end
def agent_email_cert
@agent_email_cert ||= File.read(File.join(fixture_dir, 'leaves', "#{agent_name}.email.issued_by.agent-ca.crt"))
end
def agent_email_key
@agent_email_cert ||= File.read(File.join(fixture_dir, 'leaves', "#{agent_name}.email.issued_by.agent-ca.key"))
end
def master_cert
@master_cert ||= File.read(File.join(fixture_dir, 'leaves', "#{master_name}.issued_by.master-ca.crt"))
end
def master_key
@master_key ||= File.read(File.join(fixture_dir, 'leaves', "#{master_name}.issued_by.master-ca.key"))
end
def master_cert_rogue
@master_cert_rogue ||= File.read(File.join(fixture_dir, 'leaves', "#{master_name}.issued_by.agent-ca.crt"))
end
def master_key_rogue
@master_key_rogue ||= File.read(File.join(fixture_dir, 'leaves', "#{master_name}.issued_by.agent-ca.key"))
end
## Configuration files
def agent_conf
@agent_conf ||= <<-EO_AGENT_CONF
[main]
color = false
certname = #{agent_name}
server = #{master_name}
certificate_revocation = false
# localcacert must contain the Root CA certificate to complete the 2 level CA
# chain when an intermediate CA certificate is being used. Either the HTTP
# server must send the intermediate certificate during the handshake, or the
# agent must use the `ssl_client_ca_auth` setting to provide the client
# certificate.
localcacert = #{test_dir}/ca_root.crt
EO_AGENT_CONF
end
def agent_conf_email
@agent_conf ||= <<-EO_AGENT_CONF
[main]
color = false
certname = #{agent_name}
server = #{master_name}
certificate_revocation = false
hostcert = #{test_dir}/agent_email.crt
hostkey = #{test_dir}/agent_email.key
localcacert = #{test_dir}/ca_root.crt
EO_AGENT_CONF
end
def agent_conf_crl
@agent_conf_crl ||= <<-EO_AGENT_CONF
[main]
certname = #{agent_name}
server = #{master_name}
# localcacert must contain the Root CA certificate to complete the 2 level CA
# chain when an intermediate CA certificate is being used. Either the HTTP
# server must send the intermediate certificate during the handshake, or the
# agent must use the `ssl_client_ca_auth` setting to provide the client
# certificate.
localcacert = #{test_dir}/ca_root.crt
EO_AGENT_CONF
end
def master_conf
@master_conf ||= <<-EO_MASTER_CONF
[master]
ca = false
certname = #{master_name}
ssl_client_header = HTTP_X_CLIENT_DN
ssl_client_verify_header = HTTP_X_CLIENT_VERIFY
EO_MASTER_CONF
end
##
# Passenger Rack compliant config.ru which is responsible for starting the
# Puppet master.
def config_ru
@config_ru ||= <<-EO_CONFIG_RU
\$0 = "master"
ARGV << "--rack"
ARGV << "--confdir=#{test_dir}/etc/master"
ARGV << "--vardir=#{test_dir}/etc/master/var"
require 'puppet/util/command_line'
run Puppet::Util::CommandLine.new.execute
EO_CONFIG_RU
end
##
# auth_conf should return auth authorization file that allows *.example.org
# access to to the full REST API.
def auth_conf
@auth_conf_content ||= File.read(File.join(fixture_dir, 'auth.conf'))
end
##
# Apache configuration with Passenger
def httpd_conf
@httpd_conf ||= <<-EO_HTTPD_CONF
User apache
Group apache
ServerRoot "/etc/httpd"
PidFile run/httpd.pid
Timeout 60
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 15
StartServers 8
MinSpareServers 5
MaxSpareServers 20
ServerLimit 256
MaxClients 256
MaxRequestsPerChild 4000
StartServers 4
MaxClients 300
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule auth_digest_module modules/mod_auth_digest.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_alias_module modules/mod_authn_alias.so
LoadModule authn_anon_module modules/mod_authn_anon.so
LoadModule authn_dbm_module modules/mod_authn_dbm.so
LoadModule authn_default_module modules/mod_authn_default.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_owner_module modules/mod_authz_owner.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_dbm_module modules/mod_authz_dbm.so
LoadModule authz_default_module modules/mod_authz_default.so
LoadModule ldap_module modules/mod_ldap.so
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
LoadModule include_module modules/mod_include.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule logio_module modules/mod_logio.so
LoadModule env_module modules/mod_env.so
LoadModule ext_filter_module modules/mod_ext_filter.so
LoadModule mime_magic_module modules/mod_mime_magic.so
LoadModule expires_module modules/mod_expires.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule headers_module modules/mod_headers.so
LoadModule usertrack_module modules/mod_usertrack.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule dav_module modules/mod_dav.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule info_module modules/mod_info.so
LoadModule dav_fs_module modules/mod_dav_fs.so
LoadModule vhost_alias_module modules/mod_vhost_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule dir_module modules/mod_dir.so
LoadModule actions_module modules/mod_actions.so
LoadModule speling_module modules/mod_speling.so
LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
LoadModule substitute_module modules/mod_substitute.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule cache_module modules/mod_cache.so
LoadModule suexec_module modules/mod_suexec.so
LoadModule disk_cache_module modules/mod_disk_cache.so
LoadModule cgi_module modules/mod_cgi.so
LoadModule version_module modules/mod_version.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule passenger_module modules/mod_passenger.so
ServerName #{master_name}
DocumentRoot "#{test_dir}/etc/master/public"
DefaultType text/plain
TypesConfig /etc/mime.types
# Same thing, just using a certificate issued by the Agent CA, which should not
# be trusted by the clients.
Listen 8140 https
Listen 8141 https
SSLEngine on
SSLProtocol ALL -SSLv2
SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:-LOW:-SSLv2:-EXP
SSLCertificateFile "#{test_dir}/master.crt"
SSLCertificateKeyFile "#{test_dir}/master.key"
# The chain file is sent to the client during handshake.
SSLCertificateChainFile "#{test_dir}/ca_master_bundle.crt"
# The CA cert file is used to authenticate clients
SSLCACertificateFile "#{test_dir}/ca_agent_bundle.crt"
SSLVerifyClient optional
SSLVerifyDepth 2
SSLOptions +StdEnvVars
RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e
DocumentRoot "#{test_dir}/etc/master/public"
PassengerRoot /usr/share/gems/gems/passenger-3.0.17
PassengerRuby /usr/bin/ruby
RackAutoDetect On
RackBaseURI /
SSLEngine on
SSLProtocol ALL -SSLv2
SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:-LOW:-SSLv2:-EXP
SSLCertificateFile "#{test_dir}/master_rogue.crt"
SSLCertificateKeyFile "#{test_dir}/master_rogue.key"
SSLCertificateChainFile "#{test_dir}/ca_agent_bundle.crt"
SSLCACertificateFile "#{test_dir}/ca_agent_bundle.crt"
SSLVerifyClient optional
SSLVerifyDepth 2
SSLOptions +StdEnvVars
RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e
DocumentRoot "#{test_dir}/etc/master/public"
PassengerRoot /usr/share/gems/gems/passenger-3.0.17
PassengerRuby /usr/bin/ruby
RackAutoDetect On
RackBaseURI /
EO_HTTPD_CONF
end
+
+ ##
+ # webserver.conf for a trustworthy master for use with Jetty
+ def jetty_webserver_conf_for_trustworthy_master
+ @jetty_webserver_conf_for_trustworthy_master ||= <<-EO_WEBSERVER_CONF
+webserver: {
+ client-auth: want
+ ssl-host: 0.0.0.0
+ ssl-port: 8140
+
+ ssl-cert: "#{test_dir}/master.crt"
+ ssl-key: "#{test_dir}/master.key"
+
+ ssl-cert-chain: "#{test_dir}/ca_master_bundle.crt"
+ ssl-ca-cert: "#{test_dir}/ca_agent_bundle.crt"
+}
+ EO_WEBSERVER_CONF
+ end
+
+ ##
+ # webserver.conf for a rogue master for use with Jetty
+ def jetty_webserver_conf_for_rogue_master
+ @jetty_webserver_conf_for_rogue_master ||= <<-EO_WEBSERVER_CONF
+webserver: {
+ client-auth: want
+ ssl-host: 0.0.0.0
+ ssl-port: 8140
+
+ ssl-cert: "#{test_dir}/master_rogue.crt"
+ ssl-key: "#{test_dir}/master_rogue.key"
+
+ ssl-cert-chain: "#{test_dir}/ca_agent_bundle.crt"
+ ssl-ca-cert: "#{test_dir}/ca_agent_bundle.crt"
+}
+ EO_WEBSERVER_CONF
+ end
+
end
end
end
diff --git a/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb b/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb
index dafef1cff..543c1d815 100644
--- a/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb
+++ b/acceptance/tests/config/puppet_manages_own_configuration_in_robust_manner.rb
@@ -1,82 +1,82 @@
# User story:
# A new user has installed puppet either from source or from a gem, which does
# not put the "puppet" user or group on the system. They run the puppet master,
# which fails because of the missing user and then correct their actions. They
# expect that after correcting their actions, puppet will work correctly.
test_name "Puppet manages its own configuration in a robust manner"
-skip_test "JVM Puppet cannot change its user while running." if @options[:is_jvm_puppet]
+skip_test "JVM Puppet cannot change its user while running." if @options[:is_puppetserver]
# when owner/group works on windows for settings, this confine should be removed.
confine :except, :platform => 'windows'
# when managhome roundtrips for solaris, this confine should be removed
confine :except, :platform => 'solaris'
# pe setup includes ownership of external directories such as the passenger
# document root, which puppet itself knows nothing about
confine :except, :type => 'pe'
# same issue for a foss passenger run
if master.is_using_passenger?
skip_test 'Cannot test with passenger.'
end
if master.use_service_scripts?
# Beaker defaults to leaving puppet running when using service scripts,
# Need to shut it down so we can modify user/group and test startup failure
on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped'))
end
step "Clear out yaml directory because of a bug in the indirector/yaml. (See #21145)"
on master, 'rm -rf $(puppet master --configprint yamldir)'
original_state = {}
step "Record original state of system users" do
hosts.each do |host|
original_state[host] = {}
original_state[host][:user] = user = host.execute('puppet config print user')
original_state[host][:group] = group = host.execute('puppet config print group')
original_state[host][:ug_resources] = on(host, puppet('resource', 'user', user)).stdout
original_state[host][:ug_resources] += on(host, puppet('resource', 'group', group)).stdout
original_state[host][:ug_resources] += "Group['#{group}'] -> User['#{user}']\n"
end
end
teardown do
# And cleaning up yaml dir again here because we are changing service
# user and group ids back to the original uid and gid
on master, 'rm -rf $(puppet master --configprint yamldir)'
hosts.each do |host|
apply_manifest_on(host, <<-ORIG)
#{original_state[host][:ug_resources]}
ORIG
end
with_puppet_running_on(master, {}) do
agents.each do |agent|
on agent, puppet('agent', '-t', '--server', master)
end
end
end
step "Remove system users" do
hosts.each do |host|
on host, puppet('resource', 'user', original_state[host][:user], 'ensure=absent')
on host, puppet('resource', 'group', original_state[host][:group], 'ensure=absent')
end
end
step "Ensure master fails to start when missing system user" do
on master, puppet('master'), :acceptable_exit_codes => [74] do
assert_match(/could not change to group "#{original_state[master][:group]}"/, result.output)
assert_match(/Could not change to user #{original_state[master][:user]}/, result.output)
end
end
step "Ensure master starts when making users after having previously failed startup" do
with_puppet_running_on(master,
:master => { :mkusers => true }) do
agents.each do |agent|
on agent, puppet('agent', '-t', '--server', master)
end
end
end
diff --git a/acceptance/tests/environment/cmdline_overrides_environment.rb b/acceptance/tests/environment/cmdline_overrides_environment.rb
index 78a07413b..bbaad76eb 100644
--- a/acceptance/tests/environment/cmdline_overrides_environment.rb
+++ b/acceptance/tests/environment/cmdline_overrides_environment.rb
@@ -1,322 +1,322 @@
test_name "Commandline modulepath and manifest settings override environment"
-skip_test "CLI-master tests are not applicable" if @options[:is_jvm_puppet]
+skip_test "CLI-master tests are not applicable" if @options[:is_puppetserver]
testdir = create_tmpdir_for_user master, 'cmdline_and_environment'
environmentpath = "#{testdir}/environments"
modulepath = "#{testdir}/modules"
manifests = "#{testdir}/manifests"
sitepp = "#{manifests}/site.pp"
other_manifestdir = "#{testdir}/other_manifests"
other_sitepp = "#{other_manifestdir}/site.pp"
other_modulepath = "#{testdir}/some_other_modulepath"
cmdline_manifest = "#{testdir}/cmdline.pp"
step "Prepare manifests and modules"
apply_manifest_on(master, <<-MANIFEST, :catch_failures => true)
File {
ensure => directory,
owner => #{master['user']},
group => #{master['group']},
mode => 0750,
}
##############################################
# A production directory environment
file {
"#{testdir}":;
"#{environmentpath}":;
"#{environmentpath}/production":;
"#{environmentpath}/production/manifests":;
"#{environmentpath}/production/modules":;
"#{environmentpath}/production/modules/amod":;
"#{environmentpath}/production/modules/amod/manifests":;
}
file { "#{environmentpath}/production/modules/amod/manifests/init.pp":
ensure => file,
mode => 0640,
content => 'class amod {
notify { "amod from production environment": }
}'
}
file { "#{environmentpath}/production/manifests/production.pp":
ensure => file,
mode => 0640,
content => '
notify { "in production.pp": }
include amod
'
}
##############################################################
# To be set as default manifests and modulepath in puppet.conf
file {
"#{modulepath}":;
"#{modulepath}/amod/":;
"#{modulepath}/amod/manifests":;
}
file { "#{modulepath}/amod/manifests/init.pp":
ensure => file,
mode => 0640,
content => 'class amod {
notify { "amod from modulepath": }
}'
}
file { "#{manifests}": }
file { "#{sitepp}":
ensure => file,
mode => 0640,
content => '
notify { "in site.pp": }
include amod
'
}
file { "#{other_manifestdir}": }
file { "#{other_sitepp}":
ensure => file,
mode => 0640,
content => '
notify { "in other manifestdir site.pp": }
include amod
'
}
################################
# To be specified on commandline
file {
"#{other_modulepath}":;
"#{other_modulepath}/amod/":;
"#{other_modulepath}/amod/manifests":;
}
file { "#{other_modulepath}/amod/manifests/init.pp":
ensure => file,
mode => 0640,
content => 'class amod {
notify { "amod from commandline modulepath": }
}'
}
file { "#{cmdline_manifest}":
ensure => file,
mode => 0640,
content => '
notify { "in cmdline.pp": }
include amod
'
}
MANIFEST
def shutdown_puppet_if_running_as_a_service
if master.use_service_scripts?
# Beaker defaults to leaving puppet running when using service scripts,
# Need to shut it down so we can start up with commandline options
on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped'))
end
end
teardown do
if master.use_service_scripts?
# Beaker defaults to leaving puppet running when using service scripts,
on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=running'))
end
end
# Note: this is the semantics seen with legacy environments if commandline
# manifest/modulepath are set.
step "CASE 1: puppet master with --manifest and --modulepath overrides set production directory environment" do
if master.is_using_passenger?
step "Skipping for Passenger (PE) setup; since the equivalent of a commandline override would be adding the setting to config.ru, which seems like a very odd thing to do."
else
shutdown_puppet_if_running_as_a_service
master_opts = {
'master' => {
'environmentpath' => environmentpath,
'manifest' => sitepp,
'modulepath' => modulepath,
},
:__service_args__ => {
:bypass_service_script => true,
},
}
master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifest=#{cmdline_manifest} --modulepath=#{other_modulepath}")
with_puppet_running_on master, master_opts_with_cmdline, testdir do
agents.each do |agent|
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do
assert_match(/in cmdline\.pp/, stdout)
assert_match(/amod from commandline modulepath/, stdout)
assert_no_match(/production/, stdout)
end
step "CASE 1a: even if environment is specified"
on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do
assert_match(/in cmdline\.pp/, stdout)
assert_match(/amod from commandline modulepath/, stdout)
assert_no_match(/production/, stdout)
end
end
end
step "CASE 2: or if you set --manifestdir" do
master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}")
step "CASE 2: it is ignored if manifest is set in puppet.conf to something not using $manifestdir"
with_puppet_running_on master, master_opts_with_cmdline, testdir do
agents.each do |agent|
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do
assert_match(/in production\.pp/, stdout)
assert_match(/amod from commandline modulepath/, stdout)
end
end
end
step "CASE 2a: but does pull in the default manifest via manifestdir if manifest is not set"
master_opts_with_cmdline = master_opts.merge(:__commandline_args__ => "--manifestdir=#{other_manifestdir} --modulepath=#{other_modulepath}")
master_opts_with_cmdline['master'].delete('manifest')
with_puppet_running_on master, master_opts_with_cmdline, testdir do
agents.each do |agent|
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2]) do
assert_match(/in other manifestdir site\.pp/, stdout)
assert_match(/amod from commandline modulepath/, stdout)
assert_no_match(/production/, stdout)
end
end
end
end
end
end
step "CASE 3: puppet master with manifest and modulepath set in puppet.conf is overriden by an existing and set production directory environment" do
master_opts = {
'master' => {
'environmentpath' => environmentpath,
'manifest' => sitepp,
'modulepath' => modulepath,
}
}
if master.is_pe?
master_opts['master']['basemodulepath'] = master['sitemoduledir']
end
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
step "CASE 3: this case is unfortunate, but will be irrelevant when we remove legacyenv in 4.0"
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do
assert_match(/in production\.pp/, stdout)
assert_match(/amod from production environment/, stdout)
end
step "CASE 3a: if environment is specified"
on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do
assert_match(/in production\.pp/, stdout)
assert_match(/amod from production environment/, stdout)
end
end
end
end
step "CASE 4: puppet master with default manifest, modulepath, environment, environmentpath and an existing '#{environmentpath}/production' directory environment that has not been set" do
if master.is_using_passenger?
step "Skipping for PE because PE requires most of the existing puppet.conf and /etc/puppetlabs/puppet configuration, and we cannot simply point to a new conf directory."
else
shutdown_puppet_if_running_as_a_service
ssldir = on(master, puppet("master --configprint ssldir")).stdout.chomp
master_opts = {
:__service_args__ => {
:bypass_service_script => true,
},
:__commandline_args__ => "--confdir=#{testdir} --ssldir=#{ssldir}"
}
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
step "CASE 4: #{environmentpath}/production directory environment does not take precedence because default environmentpath is ''"
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do
assert_match(/in site\.pp/, stdout)
assert_match(/amod from modulepath/, stdout)
end
on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2]) do
assert_match(/in site\.pp/, stdout)
assert_match(/amod from modulepath/, stdout)
end
end
end
end
end
step "CASE 5: puppet master with explicit dynamic environment settings and empty environmentpath" do
step "CASE 5: Prepare an additional modulepath module"
apply_manifest_on(master, <<-MANIFEST, :catch_failures => true)
File {
ensure => directory,
owner => #{master['user']},
group => #{master['group']},
mode => 0750,
}
# A second module in another modules dir
file {
"#{other_modulepath}":;
"#{other_modulepath}/bmod/":;
"#{other_modulepath}/bmod/manifests":;
}
file { "#{other_modulepath}/bmod/manifests/init.pp":
ensure => file,
mode => 0640,
content => 'class bmod {
notify { "bmod from other modulepath": }
}'
}
file { "#{environmentpath}/production/manifests/production.pp":
ensure => file,
mode => 0640,
content => '
notify { "in production.pp": }
include amod
include bmod
'
}
MANIFEST
master_opts = {
'master' => {
'manifest' => "#{environmentpath}/$environment/manifests",
'modulepath' => "#{environmentpath}/$environment/modules:#{other_modulepath}",
}
}
if master.is_pe?
master_opts['master']['modulepath'] << ":#{master['sitemoduledir']}"
end
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
step "CASE 5: pulls in the production environment based on $environment default"
on(agent, puppet("agent -t --server #{master}"), :acceptable_exit_codes => [2] ) do
assert_match(/in production\.pp/, stdout)
assert_match(/amod from production environment/, stdout)
step "CASE 5: and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)"
assert_match(/bmod from other modulepath/, stdout)
end
step "CASE 5a: pulls in the production environment when explicitly set"
on(agent, puppet("agent -t --server #{master} --environment production"), :acceptable_exit_codes => [2] ) do
assert_match(/in production\.pp/, stdout)
assert_match(/amod from production environment/, stdout)
step "CASE 5a: and sees modules located in later elements of the modulepath (which would not be seen by a directory env (PUP-2158)"
assert_match(/bmod from other modulepath/, stdout)
end
end
end
end
diff --git a/acceptance/tests/external_ca_support/apache_external_root_ca.rb b/acceptance/tests/external_ca_support/apache_external_root_ca.rb
index 39bd194c1..b78d748d9 100644
--- a/acceptance/tests/external_ca_support/apache_external_root_ca.rb
+++ b/acceptance/tests/external_ca_support/apache_external_root_ca.rb
@@ -1,203 +1,203 @@
begin
require 'puppet_x/acceptance/external_cert_fixtures'
rescue LoadError
$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
require 'puppet_x/acceptance/external_cert_fixtures'
end
# This test only runs on EL-6 master roles.
confine :to, :platform => 'el-6'
confine :except, :type => 'pe'
-skip_test "Test not supported on jvm" if @options[:is_jvm_puppet]
+skip_test "Test not supported on jvm" if @options[:is_puppetserver]
if master.use_service_scripts?
# Beaker defaults to leaving puppet running when using service scripts,
# Need to shut it down so we can start up our apache instance
on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped'))
teardown do
# And ensure that it is up again after everything is done
on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=running'))
end
end
# Verify that a trivial manifest can be run to completion.
# Supported Setup: Single, Root CA
# - Agent and Master SSL cert issued by the Root CA
# - Revocation disabled on the agent `certificate_revocation = false`
# - CA disabled on the master `ca = false`
#
# SUPPORT NOTES
#
# * If the x509 alt names extension is used when issuing SSL server certificates
# for the Puppet master, then the client SSL certificate issued by an external
# CA must posses the DNS common name in the alternate name field. This is
# due to a bug in Ruby. If the CN is not duplicated in the Alt Names, then
# the following error will appear on the agent with MRI 1.8.7:
#
# Warning: Server hostname 'master1.example.org' did not match server
# certificate; expected one of master1.example.org, DNS:puppet,
# DNS:master-ca.example.org
#
# See: https://bugs.ruby-lang.org/issues/6493
test_name "Puppet agent works with Apache, both configured with externally issued certificates from independent intermediate CA's"
step "Copy certificates and configuration files to the master..."
fixture_dir = File.expand_path('../fixtures', __FILE__)
testdir = master.tmpdir('apache_external_root_ca')
fixtures = PuppetX::Acceptance::ExternalCertFixtures.new(fixture_dir, testdir)
# We need this variable in scope.
disable_and_reenable_selinux = nil
# Register our cleanup steps early in a teardown so that they will happen even
# if execution aborts part way.
teardown do
step "Cleanup Apache (httpd) and /etc/hosts"
# Restore /etc/hosts
on master, "cp -p '#{testdir}/hosts' /etc/hosts"
# stop the service before moving files around
on master, "/etc/init.d/httpd stop"
on master, "mv --force /etc/httpd/conf/httpd.conf{,.external_ca_test}"
on master, "mv --force /etc/httpd/conf/httpd.conf{.orig,}"
if disable_and_reenable_selinux
step "Restore the original state of SELinux"
on master, "setenforce 1"
end
end
# Read all of the CA certificates.
# Copy all of the x.509 fixture data over to the master.
create_remote_file master, "#{testdir}/ca_root.crt", fixtures.root_ca_cert
create_remote_file master, "#{testdir}/ca_agent.crt", fixtures.agent_ca_cert
create_remote_file master, "#{testdir}/ca_master.crt", fixtures.master_ca_cert
create_remote_file master, "#{testdir}/ca_master.crl", fixtures.master_ca_crl
create_remote_file master, "#{testdir}/ca_master_bundle.crt", "#{fixtures.master_ca_cert}\n#{fixtures.root_ca_cert}\n"
create_remote_file master, "#{testdir}/ca_agent_bundle.crt", "#{fixtures.agent_ca_cert}\n#{fixtures.root_ca_cert}\n"
create_remote_file master, "#{testdir}/agent.crt", fixtures.agent_cert
create_remote_file master, "#{testdir}/agent.key", fixtures.agent_key
create_remote_file master, "#{testdir}/agent_email.crt", fixtures.agent_email_cert
create_remote_file master, "#{testdir}/agent_email.key", fixtures.agent_email_key
create_remote_file master, "#{testdir}/master.crt", fixtures.master_cert
create_remote_file master, "#{testdir}/master.key", fixtures.master_key
create_remote_file master, "#{testdir}/master_rogue.crt", fixtures.master_cert_rogue
create_remote_file master, "#{testdir}/master_rogue.key", fixtures.master_key_rogue
##
# Now create the master and agent puppet.conf
#
# We need to create the public directory for Passenger and the modules
# directory to avoid `Error: Could not evaluate: Could not retrieve information
# from environment production source(s) puppet://master1.example.org/plugins`
on master, "mkdir -p #{testdir}/etc/{master/{public,modules/empty/lib},agent}"
# Backup /etc/hosts
on master, "cp -p /etc/hosts '#{testdir}/hosts'"
# Make master1.example.org resolve if it doesn't already.
on master, "grep -q -x '#{fixtures.host_entry}' /etc/hosts || echo '#{fixtures.host_entry}' >> /etc/hosts"
create_remote_file master, "#{testdir}/etc/agent/puppet.conf", fixtures.agent_conf
create_remote_file master, "#{testdir}/etc/agent/puppet.conf.crl", fixtures.agent_conf_crl
create_remote_file master, "#{testdir}/etc/agent/puppet.conf.email", fixtures.agent_conf_email
create_remote_file master, "#{testdir}/etc/master/puppet.conf", fixtures.master_conf
# auth.conf to allow *.example.com access to the rest API
create_remote_file master, "#{testdir}/etc/master/auth.conf", fixtures.auth_conf
create_remote_file master, "#{testdir}/etc/master/config.ru", fixtures.config_ru
step "Set filesystem permissions and ownership for the master"
# These permissions are required for Passenger to start Puppet as puppet
on master, "chown -R puppet:puppet #{testdir}/etc/master"
# These permissions are just for testing, end users should protect their
# private keys.
on master, "chmod -R a+rX #{testdir}"
agent_cmd_prefix = "--confdir #{testdir}/etc/agent --vardir #{testdir}/etc/agent/var"
step "Configure EPEL"
epel_release_path = "http://mirror.us.leaseweb.net/epel/6/i386/epel-release-6-8.noarch.rpm"
on master, "rpm -q epel-release || (yum -y install #{epel_release_path} && yum -y upgrade epel-release)"
step "Configure Apache and Passenger"
packages = [ 'httpd', 'mod_ssl', 'mod_passenger', 'rubygem-passenger', 'policycoreutils-python' ]
packages.each do |pkg|
on master, "rpm -q #{pkg} || (yum -y install #{pkg})"
end
create_remote_file master, "#{testdir}/etc/httpd.conf", fixtures.httpd_conf
on master, 'test -f /etc/httpd/conf/httpd.conf.orig || cp -p /etc/httpd/conf/httpd.conf{,.orig}'
on master, "cat #{testdir}/etc/httpd.conf > /etc/httpd/conf/httpd.conf"
step "Make SELinux and Apache play nicely together..."
on master, "sestatus" do
if stdout.match(/Current mode:.*enforcing/)
disable_and_reenable_selinux = true
else
disable_and_reenable_selinux = false
end
end
if disable_and_reenable_selinux
on master, "setenforce 0"
end
step "Start the Apache httpd service..."
on master, 'service httpd restart'
# Move the agent SSL cert and key into place.
# The filename must match the configured certname, otherwise Puppet will try
# and generate a new certificate and key
step "Configure the agent with the externally issued certificates"
on master, "mkdir -p #{testdir}/etc/agent/ssl/{public_keys,certs,certificate_requests,private_keys,private}"
create_remote_file master, "#{testdir}/etc/agent/ssl/certs/#{fixtures.agent_name}.pem", fixtures.agent_cert
create_remote_file master, "#{testdir}/etc/agent/ssl/private_keys/#{fixtures.agent_name}.pem", fixtures.agent_key
# Now, try and run the agent on the master against itself.
step "Successfully run the puppet agent on the master"
on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
assert_no_match /Creating a new SSL key/, stdout
assert_no_match /\Wfailed\W/i, stderr
assert_no_match /\Wfailed\W/i, stdout
assert_no_match /\Werror\W/i, stderr
assert_no_match /\Werror\W/i, stdout
# Assert the exit code so we get a "Failed test" instead of an "Errored test"
assert exit_code == 0
end
step "Agent refuses to connect to a rogue master"
on master, puppet_agent("#{agent_cmd_prefix} --ssl_client_ca_auth=#{testdir}/ca_master.crt --masterport=8141 --test"), :acceptable_exit_codes => (0..255) do
assert_no_match /Creating a new SSL key/, stdout
assert_match /certificate verify failed/i, stderr
assert_match /The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file/i, stderr
assert exit_code == 1
end
step "Master accepts client cert with email address in subject"
on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_email}"
on master, "cp #{testdir}/etc/agent/puppet.conf{.email,}"
on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
assert_no_match /\Wfailed\W/i, stdout
assert_no_match /\Wfailed\W/i, stderr
assert_no_match /\Werror\W/i, stdout
assert_no_match /\Werror\W/i, stderr
# Assert the exit code so we get a "Failed test" instead of an "Errored test"
assert exit_code == 0
end
step "Agent refuses to connect to revoked master"
on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_crl}"
on master, "cp #{testdir}/etc/agent/puppet.conf{.crl,}"
revoke_opts = "--hostcrl #{testdir}/ca_master.crl"
on master, puppet_agent("#{agent_cmd_prefix} #{revoke_opts} --test"), :acceptable_exit_codes => (0..255) do
assert_match /certificate revoked.*?example.org/, stderr
assert exit_code == 1
end
step "Finished testing External Certificates"
diff --git a/acceptance/tests/external_ca_support/apache_external_root_ca.rb b/acceptance/tests/external_ca_support/jetty_external_root_ca.rb
similarity index 52%
copy from acceptance/tests/external_ca_support/apache_external_root_ca.rb
copy to acceptance/tests/external_ca_support/jetty_external_root_ca.rb
index 39bd194c1..be768b891 100644
--- a/acceptance/tests/external_ca_support/apache_external_root_ca.rb
+++ b/acceptance/tests/external_ca_support/jetty_external_root_ca.rb
@@ -1,203 +1,172 @@
begin
require 'puppet_x/acceptance/external_cert_fixtures'
rescue LoadError
$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
require 'puppet_x/acceptance/external_cert_fixtures'
end
-# This test only runs on EL-6 master roles.
-confine :to, :platform => 'el-6'
confine :except, :type => 'pe'
-skip_test "Test not supported on jvm" if @options[:is_jvm_puppet]
-
-if master.use_service_scripts?
- # Beaker defaults to leaving puppet running when using service scripts,
- # Need to shut it down so we can start up our apache instance
- on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=stopped'))
-
- teardown do
- # And ensure that it is up again after everything is done
- on(master, puppet('resource', 'service', master['puppetservice'], 'ensure=running'))
- end
-end
+skip_test "Test only supported on Jetty" unless @options[:is_puppetserver]
# Verify that a trivial manifest can be run to completion.
# Supported Setup: Single, Root CA
# - Agent and Master SSL cert issued by the Root CA
# - Revocation disabled on the agent `certificate_revocation = false`
# - CA disabled on the master `ca = false`
#
# SUPPORT NOTES
#
# * If the x509 alt names extension is used when issuing SSL server certificates
# for the Puppet master, then the client SSL certificate issued by an external
# CA must posses the DNS common name in the alternate name field. This is
# due to a bug in Ruby. If the CN is not duplicated in the Alt Names, then
# the following error will appear on the agent with MRI 1.8.7:
#
# Warning: Server hostname 'master1.example.org' did not match server
# certificate; expected one of master1.example.org, DNS:puppet,
# DNS:master-ca.example.org
#
# See: https://bugs.ruby-lang.org/issues/6493
-test_name "Puppet agent works with Apache, both configured with externally issued certificates from independent intermediate CA's"
+test_name "Puppet agent and master work when both configured with externally issued certificates from independent intermediate CAs"
step "Copy certificates and configuration files to the master..."
fixture_dir = File.expand_path('../fixtures', __FILE__)
-testdir = master.tmpdir('apache_external_root_ca')
+testdir = master.tmpdir('jetty_external_root_ca')
fixtures = PuppetX::Acceptance::ExternalCertFixtures.new(fixture_dir, testdir)
-# We need this variable in scope.
-disable_and_reenable_selinux = nil
+jetty_confdir = master['puppetserver-confdir']
# Register our cleanup steps early in a teardown so that they will happen even
# if execution aborts part way.
teardown do
- step "Cleanup Apache (httpd) and /etc/hosts"
- # Restore /etc/hosts
+ step "Restore /etc/hosts and webserver.conf"
on master, "cp -p '#{testdir}/hosts' /etc/hosts"
- # stop the service before moving files around
- on master, "/etc/init.d/httpd stop"
- on master, "mv --force /etc/httpd/conf/httpd.conf{,.external_ca_test}"
- on master, "mv --force /etc/httpd/conf/httpd.conf{.orig,}"
-
- if disable_and_reenable_selinux
- step "Restore the original state of SELinux"
- on master, "setenforce 1"
- end
+ on master, "cp -p '#{testdir}/webserver.conf.orig' '#{jetty_confdir}/webserver.conf'"
end
# Read all of the CA certificates.
# Copy all of the x.509 fixture data over to the master.
create_remote_file master, "#{testdir}/ca_root.crt", fixtures.root_ca_cert
create_remote_file master, "#{testdir}/ca_agent.crt", fixtures.agent_ca_cert
create_remote_file master, "#{testdir}/ca_master.crt", fixtures.master_ca_cert
create_remote_file master, "#{testdir}/ca_master.crl", fixtures.master_ca_crl
create_remote_file master, "#{testdir}/ca_master_bundle.crt", "#{fixtures.master_ca_cert}\n#{fixtures.root_ca_cert}\n"
create_remote_file master, "#{testdir}/ca_agent_bundle.crt", "#{fixtures.agent_ca_cert}\n#{fixtures.root_ca_cert}\n"
create_remote_file master, "#{testdir}/agent.crt", fixtures.agent_cert
create_remote_file master, "#{testdir}/agent.key", fixtures.agent_key
create_remote_file master, "#{testdir}/agent_email.crt", fixtures.agent_email_cert
create_remote_file master, "#{testdir}/agent_email.key", fixtures.agent_email_key
create_remote_file master, "#{testdir}/master.crt", fixtures.master_cert
create_remote_file master, "#{testdir}/master.key", fixtures.master_key
create_remote_file master, "#{testdir}/master_rogue.crt", fixtures.master_cert_rogue
create_remote_file master, "#{testdir}/master_rogue.key", fixtures.master_key_rogue
##
# Now create the master and agent puppet.conf
#
# We need to create the public directory for Passenger and the modules
# directory to avoid `Error: Could not evaluate: Could not retrieve information
# from environment production source(s) puppet://master1.example.org/plugins`
on master, "mkdir -p #{testdir}/etc/{master/{public,modules/empty/lib},agent}"
# Backup /etc/hosts
on master, "cp -p /etc/hosts '#{testdir}/hosts'"
# Make master1.example.org resolve if it doesn't already.
on master, "grep -q -x '#{fixtures.host_entry}' /etc/hosts || echo '#{fixtures.host_entry}' >> /etc/hosts"
create_remote_file master, "#{testdir}/etc/agent/puppet.conf", fixtures.agent_conf
create_remote_file master, "#{testdir}/etc/agent/puppet.conf.crl", fixtures.agent_conf_crl
create_remote_file master, "#{testdir}/etc/agent/puppet.conf.email", fixtures.agent_conf_email
-create_remote_file master, "#{testdir}/etc/master/puppet.conf", fixtures.master_conf
# auth.conf to allow *.example.com access to the rest API
create_remote_file master, "#{testdir}/etc/master/auth.conf", fixtures.auth_conf
create_remote_file master, "#{testdir}/etc/master/config.ru", fixtures.config_ru
step "Set filesystem permissions and ownership for the master"
-# These permissions are required for Passenger to start Puppet as puppet
+# These permissions are required for the JVM to start Puppet as puppet
on master, "chown -R puppet:puppet #{testdir}/etc/master"
+on master, "chown -R puppet:puppet #{testdir}/*.crt"
+on master, "chown -R puppet:puppet #{testdir}/*.key"
+on master, "chown -R puppet:puppet #{testdir}/*.crl"
# These permissions are just for testing, end users should protect their
# private keys.
on master, "chmod -R a+rX #{testdir}"
agent_cmd_prefix = "--confdir #{testdir}/etc/agent --vardir #{testdir}/etc/agent/var"
-step "Configure EPEL"
-epel_release_path = "http://mirror.us.leaseweb.net/epel/6/i386/epel-release-6-8.noarch.rpm"
-on master, "rpm -q epel-release || (yum -y install #{epel_release_path} && yum -y upgrade epel-release)"
-
-step "Configure Apache and Passenger"
-packages = [ 'httpd', 'mod_ssl', 'mod_passenger', 'rubygem-passenger', 'policycoreutils-python' ]
-packages.each do |pkg|
- on master, "rpm -q #{pkg} || (yum -y install #{pkg})"
-end
-
-create_remote_file master, "#{testdir}/etc/httpd.conf", fixtures.httpd_conf
-on master, 'test -f /etc/httpd/conf/httpd.conf.orig || cp -p /etc/httpd/conf/httpd.conf{,.orig}'
-on master, "cat #{testdir}/etc/httpd.conf > /etc/httpd/conf/httpd.conf"
-
-step "Make SELinux and Apache play nicely together..."
-
-on master, "sestatus" do
- if stdout.match(/Current mode:.*enforcing/)
- disable_and_reenable_selinux = true
- else
- disable_and_reenable_selinux = false
- end
-end
-
-if disable_and_reenable_selinux
- on master, "setenforce 0"
-end
-
-step "Start the Apache httpd service..."
-on master, 'service httpd restart'
-
# Move the agent SSL cert and key into place.
# The filename must match the configured certname, otherwise Puppet will try
# and generate a new certificate and key
step "Configure the agent with the externally issued certificates"
on master, "mkdir -p #{testdir}/etc/agent/ssl/{public_keys,certs,certificate_requests,private_keys,private}"
create_remote_file master, "#{testdir}/etc/agent/ssl/certs/#{fixtures.agent_name}.pem", fixtures.agent_cert
create_remote_file master, "#{testdir}/etc/agent/ssl/private_keys/#{fixtures.agent_name}.pem", fixtures.agent_key
-# Now, try and run the agent on the master against itself.
-step "Successfully run the puppet agent on the master"
-on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
- assert_no_match /Creating a new SSL key/, stdout
- assert_no_match /\Wfailed\W/i, stderr
- assert_no_match /\Wfailed\W/i, stdout
- assert_no_match /\Werror\W/i, stderr
- assert_no_match /\Werror\W/i, stdout
- # Assert the exit code so we get a "Failed test" instead of an "Errored test"
- assert exit_code == 0
-end
+on master, "cp -p '#{jetty_confdir}/webserver.conf' '#{testdir}/webserver.conf.orig'"
+create_remote_file master, "#{jetty_confdir}/webserver.conf",
+ fixtures.jetty_webserver_conf_for_trustworthy_master
+
+master_opts = {
+ 'master' => {
+ 'ca' => false,
+ 'certname' => fixtures.master_name,
+ 'ssl_client_header' => "HTTP_X_CLIENT_DN",
+ 'ssl_client_verify_header' => "HTTP_X_CLIENT_VERIFY"
+ }
+}
+
+step "Start the Puppet master service..."
+with_puppet_running_on(master, master_opts) do
+ # Now, try and run the agent on the master against itself.
+ step "Successfully run the puppet agent on the master"
+ on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
+ assert_no_match /Creating a new SSL key/, stdout
+ assert_no_match /\Wfailed\W/i, stderr
+ assert_no_match /\Wfailed\W/i, stdout
+ assert_no_match /\Werror\W/i, stderr
+ assert_no_match /\Werror\W/i, stdout
+ # Assert the exit code so we get a "Failed test" instead of an "Errored test"
+ assert exit_code == 0
+ end
-step "Agent refuses to connect to a rogue master"
-on master, puppet_agent("#{agent_cmd_prefix} --ssl_client_ca_auth=#{testdir}/ca_master.crt --masterport=8141 --test"), :acceptable_exit_codes => (0..255) do
- assert_no_match /Creating a new SSL key/, stdout
- assert_match /certificate verify failed/i, stderr
- assert_match /The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file/i, stderr
- assert exit_code == 1
-end
+ step "Master accepts client cert with email address in subject"
+ on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_email}"
+ on master, "cp #{testdir}/etc/agent/puppet.conf{.email,}"
+ on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
+ assert_no_match /\Wfailed\W/i, stdout
+ assert_no_match /\Wfailed\W/i, stderr
+ assert_no_match /\Werror\W/i, stdout
+ assert_no_match /\Werror\W/i, stderr
+ # Assert the exit code so we get a "Failed test" instead of an "Errored test"
+ assert exit_code == 0
+ end
+
+ step "Agent refuses to connect to revoked master"
+ on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_crl}"
+ on master, "cp #{testdir}/etc/agent/puppet.conf{.crl,}"
-step "Master accepts client cert with email address in subject"
-on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_email}"
-on master, "cp #{testdir}/etc/agent/puppet.conf{.email,}"
-on master, puppet_agent("#{agent_cmd_prefix} --test"), :acceptable_exit_codes => (0..255) do
- assert_no_match /\Wfailed\W/i, stdout
- assert_no_match /\Wfailed\W/i, stderr
- assert_no_match /\Werror\W/i, stdout
- assert_no_match /\Werror\W/i, stderr
- # Assert the exit code so we get a "Failed test" instead of an "Errored test"
- assert exit_code == 0
+ revoke_opts = "--hostcrl #{testdir}/ca_master.crl"
+ on master, puppet_agent("#{agent_cmd_prefix} #{revoke_opts} --test"), :acceptable_exit_codes => (0..255) do
+ assert_match /certificate revoked.*?example.org/, stderr
+ assert exit_code == 1
+ end
end
-step "Agent refuses to connect to revoked master"
-on master, "cp #{testdir}/etc/agent/puppet.conf{,.no_crl}"
-on master, "cp #{testdir}/etc/agent/puppet.conf{.crl,}"
+create_remote_file master, "#{jetty_confdir}/webserver.conf",
+ fixtures.jetty_webserver_conf_for_rogue_master
-revoke_opts = "--hostcrl #{testdir}/ca_master.crl"
-on master, puppet_agent("#{agent_cmd_prefix} #{revoke_opts} --test"), :acceptable_exit_codes => (0..255) do
- assert_match /certificate revoked.*?example.org/, stderr
- assert exit_code == 1
+with_puppet_running_on(master, master_opts) do
+ step "Agent refuses to connect to a rogue master"
+ on master, puppet_agent("#{agent_cmd_prefix} --ssl_client_ca_auth=#{testdir}/ca_master.crt --test"), :acceptable_exit_codes => (0..255) do
+ assert_no_match /Creating a new SSL key/, stdout
+ assert_match /certificate verify failed/i, stderr
+ assert_match /The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file/i, stderr
+ assert exit_code == 1
+ end
end
step "Finished testing External Certificates"
diff --git a/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb b/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb
index 115e69e2c..d43f6ca42 100644
--- a/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb
+++ b/acceptance/tests/helpful_error_message_when_hostname_not_match_server_certificate.rb
@@ -1,18 +1,45 @@
test_name "generate a helpful error message when hostname doesn't match server certificate"
-skip_test "Certs need to be signed with DNS Alt names." if @options[:is_jvm_puppet]
skip_test( 'Changing certnames of the master will break PE/Passenger installations' ) if master.is_using_passenger?
+certname = "foobar_not_my_hostname"
+dns_alt_names = "one_cert,two_cert,red_cert,blue_cert"
+
+# The DNS names in the certificate's Subject Alternative Name extension
+# may appear in any order so sort the list of names alphabetically before
+# comparison.
+expected_sorted_dns_alt_names = "DNS:" +
+ dns_alt_names.split(",").push(certname).sort().join(", DNS:")
+
# Start the master with a certname not matching its hostname
master_opts = {
'master' => {
- 'certname' => 'foobar_not_my_hostname',
- 'dns_alt_names' => 'one_cert,two_cert,red_cert,blue_cert'
+ 'certname' => certname,
+ 'dns_alt_names' => dns_alt_names
}
}
+
with_puppet_running_on master, master_opts do
run_agent_on(agents, "--test --server #{master}", :acceptable_exit_codes => (1..255)) do
- msg = "Server hostname '#{master}' did not match server certificate; expected one of foobar_not_my_hostname, DNS:blue_cert, DNS:foobar_not_my_hostname, DNS:one_cert, DNS:red_cert, DNS:two_cert"
- assert_match(msg, stderr)
+ msg = "Server hostname '" +
+ Regexp.escape(master) +
+ "' did not match server certificate; expected one of " +
+ Regexp.escape(certname) +
+ ', (.*)$'
+
+ exp = Regexp.new (msg)
+
+ match_result = exp.match(stderr)
+
+ assert(match_result, "Expected " + msg + " to match '" + stderr + "'")
+
+ # Sort the expected DNS names in alphabetical order before comparison.
+ # The names extracted from the shell output might contain color output
+ # characters at the end (\e[0m), so strip those off before sorting.
+ actual_sorted_dns_alt_names = match_result[1].sub(/\e\[0m$/,'').
+ split(", ").sort().join(", ")
+
+ assert_equal(expected_sorted_dns_alt_names, actual_sorted_dns_alt_names,
+ "Unexpected DNS alt names found in server certificate")
end
end
diff --git a/acceptance/tests/loader/func4x_loadable_from_modules.rb b/acceptance/tests/loader/func4x_loadable_from_modules.rb
new file mode 100644
index 000000000..53690a9ec
--- /dev/null
+++ b/acceptance/tests/loader/func4x_loadable_from_modules.rb
@@ -0,0 +1,76 @@
+test_name "Exercise a module with 4x function and 4x system function"
+
+# Purpose:
+# Test that a packed puppet can call a 4x system function, and that a 4x function in
+# a module can be called.
+#
+# Method:
+# * Manually construct a very simple module with a manifest that creates a file.
+# * The file has content that depends on logic that calls both a system function (reduce), and
+# a function supplied in the module (helloworld::mul10).
+# * The module is manually constructed to allow the test to also run on Windows where the module tool
+# is not supported.
+# * The module is included by calling 'include' from 'puppet apply'.
+# * Puppet apply is executed to generate the file with the content.
+# * The generated contents is asserted.
+
+# TODO: The test can be improved by adding yet another module that calls the function in helloworld.
+# TODO: The test can be improved to also test loading of a non namespaced function
+
+require 'puppet/acceptance/temp_file_utils'
+extend Puppet::Acceptance::TempFileUtils
+initialize_temp_dirs
+
+agents.each do |agent|
+ # The modulepath to use in environment 'dev'
+ envs_path = get_test_file_path(agent, 'environments')
+ dev_modulepath = get_test_file_path(agent, 'environments/dev/modules')
+ target_path = get_test_file_path(agent, 'output')
+ mkdirs agent, target_path
+
+ # make sure that we use the modulepath from the dev environment
+ puppetconf = get_test_file_path(agent, 'puppet.conf')
+ on agent, puppet("config", "set", "environment", "dev", "--section", "user", "--config", puppetconf)
+ on agent, puppet("config", "set", "environmentpath", envs_path, "--section", "main", "--config", puppetconf)
+
+ # Where the functions in the written modules should go
+ helloworld_functions = 'helloworld/lib/puppet/functions/helloworld'
+ # Clean out the module that will be written to ensure no interference from a previous run
+ on agent, "rm -rf #{File.join(dev_modulepath, 'helloworld')}"
+ mkdirs agent, File.join(dev_modulepath, helloworld_functions)
+
+ # Write a module
+ # Write the function helloworld::mul10, that multiplies its argument by 10
+ create_remote_file(agent, File.join(dev_modulepath, helloworld_functions, "mul10.rb"), <<'SOURCE')
+Puppet::Functions.create_function(:'helloworld::mul10') do
+ def mul10(x)
+ x * 10
+ end
+end
+SOURCE
+
+ # Write a manifest that calls a 4x function (reduce), and calls a function defined in the module
+ # (helloworld::mul10).
+ #
+ mkdirs agent, File.join(dev_modulepath, "helloworld", "manifests")
+ create_remote_file(agent, File.join(dev_modulepath, "helloworld", "manifests", "init.pp"), < 'file',
+ mode => '0666',
+ content => [1,2,3].reduce("Generated") |$memo, $n| {
+ "${memo}, ${n} => ${helloworld::mul10($n)}"
+ }
+ }
+}
+SOURCE
+
+ # Run apply to generate the file with the output
+ on agent, puppet('apply', '--parser', 'future', '-e', "'include helloworld'", '--config', puppetconf)
+
+ # Assert that the file was written with the generated content
+ on(agent, "cat #{File.join(target_path, 'result.txt')}") do
+ assert_match(/^Generated, 1 => 10, 2 => 20, 3 => 30$/, stdout, "Generated the wrong content")
+ end
+
+end
diff --git a/acceptance/tests/node/check_woy_cache_works.rb b/acceptance/tests/node/check_woy_cache_works.rb
index 57d7b89ac..881d1aa20 100644
--- a/acceptance/tests/node/check_woy_cache_works.rb
+++ b/acceptance/tests/node/check_woy_cache_works.rb
@@ -1,47 +1,55 @@
require 'securerandom'
require 'puppet/acceptance/temp_file_utils'
require 'yaml'
extend Puppet::Acceptance::TempFileUtils
test_name "ticket #16753 node data should be cached in yaml to allow it to be queried"
node_name = "woy_node_#{SecureRandom.hex}"
auth_contents = < {
- 'rest_authconfig' => authfile
+ 'rest_authconfig' => authfile,
+ 'yamldir' => temp_yamldir,
}
}
with_puppet_running_on master, master_opts do
# only one agent is needed because we only care about the file written on the master
run_agent_on(agents[0], "--no-daemonize --verbose --onetime --node_name_value #{node_name} --server #{master}")
yamldir = on(master, puppet('master', '--configprint', 'yamldir')).stdout.chomp
on master, puppet('node', 'search', '"*"', '--node_terminus', 'yaml', '--clientyamldir', yamldir, '--render-as', 'json') do
assert_match(/"name":["\s]*#{node_name}/, stdout,
"Expect node name '#{node_name}' to be present in node yaml content written by the WriteOnlyYaml terminus")
end
end
diff --git a/acceptance/tests/resource/service/ticket_14297_handle_upstart.rb b/acceptance/tests/resource/service/ticket_14297_handle_upstart.rb
index 966705583..1400443f1 100644
--- a/acceptance/tests/resource/service/ticket_14297_handle_upstart.rb
+++ b/acceptance/tests/resource/service/ticket_14297_handle_upstart.rb
@@ -1,52 +1,58 @@
test_name 'Upstart Testing'
# only run these on ubuntu vms
confine :to, :platform => 'ubuntu'
# pick any ubuntu agent
agent = agents.first
-def check_service_for(pkg, type, agent)
- if pkg == "apache2"
- if type == "stop"
- on agent, "service #{pkg} status", :acceptable_exit_codes => [1,2,3]
+def manage_service_for(pkg, state, agent)
+
+ return_code = 0
+
+ if pkg == 'rabbitmq-server' && state == 'stopped'
+ return_code = 3
+ end
+
+ manifest = <<-MANIFEST
+ service { '#{pkg}':
+ ensure => #{state},
+ } ~>
+ exec { 'service #{pkg} status':
+ path => $path,
+ logoutput => true,
+ returns => #{return_code},
+ }
+ MANIFEST
+
+ apply_manifest_on(agent, manifest, :catch_failures => true) do
+ if pkg == 'rabbitmq-server'
+ if state == 'running'
+ assert_match(/Status of node/m, stdout, "Could not start #{pkg}.")
+ elsif
+ assert_match(/unable to connect to node/m, stdout, "Could not stop #{pkg}.")
+ end
else
- on agent, "service #{pkg} status", :acceptable_exit_codes => [0]
+ if state == 'running'
+ assert_match(/start/m, stdout, "Could not start #{pkg}.")
+ elsif
+ assert_match(/stop/m, stdout, "Could not stop #{pkg}.")
+ end
end
- else
- on agent, "service #{pkg} status | grep #{type} -q"
end
end
begin
# in Precise these packages provide a mix of upstart with no linked init
# script (tty2), upstart linked to an init script (rsyslog), and no upstart
-# script - only an init script (apache2)
- %w(tty2 rsyslog apache2).each do |pkg|
- on agent, puppet_resource("package #{pkg} ensure=present")
-
- step "Ensure #{pkg} has started"
- on agent, "service #{pkg} start", :acceptable_exit_codes => [0,1]
+# script - only an init script (rabbitmq-server)
+ %w(tty2 rsyslog rabbitmq-server).each do |pkg|
- step "Check that status for running #{pkg}"
- check_service_for(pkg, "start", agent)
-
- step "Stop #{pkg} with `puppet resource'"
- on agent, puppet_resource("service #{pkg} ensure=stopped")
-
- step "Check that status for stopped #{pkg}"
- check_service_for(pkg, "stop", agent)
-
- step "Start #{pkg} with `puppet resource'"
- on agent, puppet_resource("service #{pkg} ensure=running")
-
- step "Check that status for started #{pkg}"
- check_service_for(pkg, "start", agent)
- end
+ on agent, puppet_resource("package #{pkg} ensure=present")
- on agent, puppet_resource("service") do
- assert_match(/service \{ 'ssh':\n.* ensure => 'running',/, stdout, "SSH isn't running, something is wrong with upstart.")
+ # Cycle the services
+ manage_service_for(pkg, "running", agent)
+ manage_service_for(pkg, "stopped", agent)
+ manage_service_for(pkg, "running", agent)
end
-ensure
- on agent, puppet_resource("package apache2 ensure=absent")
end
diff --git a/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb b/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb
index c50cda655..135ebf0c4 100644
--- a/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb
+++ b/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb
@@ -1,121 +1,123 @@
test_name "ENC node information is used when store configs enabled (#16698)"
confine :to, :platform => ['debian', 'ubuntu']
confine :except, :platform => 'lucid'
+skip_test "Test not supported on jvm" if @options[:is_puppetserver]
+
testdir = master.tmpdir('use_enc')
create_remote_file master, "#{testdir}/enc.rb", < [],
'parameters' => {
'data' => 'data from enc'
},
}.to_yaml)
END
on master, "chmod 755 #{testdir}/enc.rb"
create_remote_file(master, "#{testdir}/site.pp", 'notify { $data: }')
on master, "chown -R #{master['user']}:#{master['group']} #{testdir}"
on master, "chmod -R g+rwX #{testdir}"
create_remote_file master, "#{testdir}/setup.pp", < $lsbmajdistrelease ? {
5 => '2.2.3',
default => '3.2.16',
},
default => '3.2.16',
}
# Trusty doesn't have a rubygems package anymore
# Not sure which other Debian's might follow suit so
# restricting this narrowly for now
#
if $lsbdistid == "Ubuntu" and $lsbdistrelease == "14.04" {
package {
activerecord:
ensure => $active_record_version,
provider => 'gem',
}
} else {
package {
rubygems:
ensure => present;
activerecord:
ensure => $active_record_version,
provider => 'gem',
require => Package[rubygems];
}
}
if $osfamily == "Debian" {
package {
# This is the deb sqlite3 package
sqlite3:
ensure => present;
libsqlite3-dev:
ensure => present,
require => Package[sqlite3];
}
} elsif $osfamily == "RedHat" {
$sqlite_gem_pkg_name = $operatingsystem ? {
"Fedora" => "rubygem-sqlite3",
default => "rubygem-sqlite3-ruby"
}
package {
sqlite:
ensure => present;
$sqlite_gem_pkg_name:
ensure => present,
require => Package[sqlite]
}
} else {
fail "Unknown OS $osfamily"
}
END
# This is a brute force hack around PUP-1073 because the deb for the core
# sqlite3 package and the rubygem for the sqlite3 driver are both named
# 'sqlite3'. So we just run a second puppet apply.
create_remote_file master, "#{testdir}/setup_sqlite_gem.pp", < 'sqlite3',
ensure => present,
provider => 'gem',
}
}
END
on master, puppet_apply("#{testdir}/setup.pp")
on master, puppet_apply("#{testdir}/setup_sqlite_gem.pp")
master_opts = {
'master' => {
'node_terminus' => 'exec',
'external_nodes' => "#{testdir}/enc.rb",
'storeconfigs' => true,
'dbadapter' => 'sqlite3',
'dblocation' => "#{testdir}/store_configs.sqlite3",
'manifest' => "#{testdir}/site.pp"
}
}
with_puppet_running_on master, master_opts, testdir do
agents.each do |agent|
run_agent_on(agent, "--no-daemonize --onetime --server #{master} --verbose")
assert_match(/data from enc/, stdout)
end
end
diff --git a/ext/systemd/puppet.service b/ext/systemd/puppet.service
index 604e47c45..3f89df8d7 100644
--- a/ext/systemd/puppet.service
+++ b/ext/systemd/puppet.service
@@ -1,12 +1,12 @@
[Unit]
Description=Puppet agent
Wants=basic.target
After=basic.target network.target puppetmaster.service
[Service]
EnvironmentFile=-/etc/sysconfig/puppetagent
EnvironmentFile=-/etc/sysconfig/puppet
-ExecStart=/usr/bin/puppet agent ${PUPPET_EXTRA_OPTS} --no-daemonize
+ExecStart=/usr/bin/puppet agent $PUPPET_EXTRA_OPTS --no-daemonize
[Install]
WantedBy=multi-user.target
diff --git a/ext/systemd/puppetmaster.service b/ext/systemd/puppetmaster.service
index 8a6f11749..7132a2694 100644
--- a/ext/systemd/puppetmaster.service
+++ b/ext/systemd/puppetmaster.service
@@ -1,11 +1,11 @@
[Unit]
Description=Puppet master
Wants=basic.target
After=basic.target network.target
[Service]
EnvironmentFile=-/etc/sysconfig/puppetmaster
-ExecStart=/usr/bin/puppet master ${PUPPETMASTER_EXTRA_OPTS} --no-daemonize
+ExecStart=/usr/bin/puppet master $PUPPETMASTER_EXTRA_OPTS --no-daemonize
[Install]
WantedBy=multi-user.target
diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb
index 4a3a4491a..8ed392ee2 100644
--- a/lib/puppet/application/resource.rb
+++ b/lib/puppet/application/resource.rb
@@ -1,228 +1,240 @@
require 'puppet/application'
class Puppet::Application::Resource < Puppet::Application
attr_accessor :host, :extra_params
def preinit
@extra_params = []
end
option("--debug","-d")
option("--verbose","-v")
option("--edit","-e")
+ option("--to_yaml","-y")
option("--host HOST","-H") do |arg|
Puppet.warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource")
@host = arg
end
option("--types", "-t") do |arg|
types = []
Puppet::Type.loadall
Puppet::Type.eachtype do |t|
next if t.name == :component
types << t.name.to_s
end
puts types.sort
exit
end
option("--param PARAM", "-p") do |arg|
@extra_params << arg.to_sym
end
def help
<<-'HELP'
puppet-resource(8) -- The resource abstraction layer shell
========
SYNOPSIS
--------
Uses the Puppet RAL to directly interact with the system.
USAGE
-----
puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit]
- [-H|--host ] [-p|--param ] [-t|--types]
+ [-H|--host ] [-p|--param ] [-t|--types] [-y|--to_yaml]
[] [= ...]
DESCRIPTION
-----------
This command provides simple facilities for converting current system
state into Puppet code, along with some ability to modify the current
state using Puppet's RAL.
By default, you must at least provide a type to list, in which case
puppet resource will tell you everything it knows about all resources of
that type. You can optionally specify an instance name, and puppet
resource will only describe that single instance.
If given a type, a name, and a series of = pairs,
puppet resource will modify the state of the specified resource.
Alternately, if given a type, a name, and the '--edit' flag, puppet
resource will write its output to a file, open that file in an editor,
and then apply the saved file as a Puppet transaction.
OPTIONS
-------
Note that any setting that's valid in the configuration
file is also a valid long argument. For example, 'ssldir' is a valid
setting, so you can specify '--ssldir ' as an
argument.
See the configuration file documentation at
http://docs.puppetlabs.com/references/stable/configuration.html for the
full list of acceptable parameters. A commented list of all
configuration options can also be generated by running puppet with
'--genconfig'.
* --debug:
Enable full debugging.
* --edit:
Write the results of the query to a file, open the file in an editor,
and read the file back in as an executable Puppet manifest.
* --host:
When specified, connect to the resource server on the named host
and retrieve the list of resouces of the type specified.
* --help:
Print this help message.
* --param:
Add more parameters to be outputted from queries.
* --types:
List all available types.
* --verbose:
Print extra information.
+* --to_yaml:
+ Output found resources in yaml format, suitable to use with Hiera and create_resources.
EXAMPLE
-------
This example uses `puppet resource` to return a Puppet configuration for
the user `luke`:
$ puppet resource user luke
user { 'luke':
home => '/home/luke',
uid => '100',
ensure => 'present',
comment => 'Luke Kanies,,,',
gid => '1000',
shell => '/bin/bash',
groups => ['sysadmin','audio','video','puppet']
}
AUTHOR
------
Luke Kanies
COPYRIGHT
---------
Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
HELP
end
def main
type, name, params = parse_args(command_line.args)
raise "You cannot edit a remote host" if options[:edit] and @host
+ raise "Editing with Yaml output is not supported" if options[:edit] and options[:to_yaml]
resources = find_or_save_resources(type, name, params)
- text = resources.
- map { |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest }.
- join("\n")
+
+ if options[:to_yaml]
+ text = resources.
+ map { |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_hierayaml }.
+ join("\n")
+ text.prepend("#{type.downcase}:\n")
+ else
+ text = resources.
+ map { |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest }.
+ join("\n")
+ end
options[:edit] ?
handle_editing(text) :
(puts text)
end
def setup
Puppet::Util::Log.newdestination(:console)
set_log_level
end
private
def remote_key(type, name)
Puppet::Resource.indirection.terminus_class = :rest
port = Puppet[:puppetport]
["https://#{@host}:#{port}", "production", "resources", type, name].join('/')
end
def local_key(type, name)
[type, name].join('/')
end
def handle_editing(text)
require 'tempfile'
# Prefer the current directory, which is more likely to be secure
# and, in the case of interactive use, accessible to the user.
tmpfile = Tempfile.new('x2puppet', Dir.pwd)
begin
# sync write, so nothing buffers before we invoke the editor.
tmpfile.sync = true
tmpfile.puts text
# edit the content
system(ENV["EDITOR"] || 'vi', tmpfile.path)
# ...and, now, pass that file to puppet to apply. Because
# many editors rename or replace the original file we need to
# feed the pathname, not the file content itself, to puppet.
system('puppet apply -v ' + tmpfile.path)
ensure
# The temporary file will be safely removed.
tmpfile.close(true)
end
end
def parse_args(args)
type = args.shift or raise "You must specify the type to display"
Puppet::Type.type(type) or raise "Could not find type #{type}"
name = args.shift
params = {}
args.each do |setting|
if setting =~ /^(\w+)=(.+)$/
params[$1] = $2
else
raise "Invalid parameter setting #{setting}"
end
end
[type, name, params]
end
def find_or_save_resources(type, name, params)
key = @host ? remote_key(type, name) : local_key(type, name)
if name
if params.empty?
[ Puppet::Resource.indirection.find( key ) ]
else
resource = Puppet::Resource.new( type, name, :parameters => params )
# save returns [resource that was saved, transaction log from applying the resource]
save_result = Puppet::Resource.indirection.save(resource, key)
[ save_result.first ]
end
else
if type == "file"
raise "Listing all file instances is not supported. Please specify a file or directory, e.g. puppet resource file /etc"
end
Puppet::Resource.indirection.search( key, {} )
end
end
end
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 7b464ee53..cb9f3714a 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -1,2078 +1,2071 @@
module Puppet
def self.default_diffargs
if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300")
""
else
"-u"
end
end
############################################################################################
# NOTE: For information about the available values for the ":type" property of settings,
# see the docs for Settings.define_settings
############################################################################################
AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).}
STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.}
# This is defined first so that the facter implementation is replaced before other setting defaults are evaluated.
define_settings(:main,
:cfacter => {
:default => false,
:type => :boolean,
:desc => 'Whether or not to use the native facter (cfacter) implementation instead of the Ruby one (facter). Defaults to false.',
:hook => proc do |value|
return unless value
raise ArgumentError, 'facter has already evaluated facts.' if Facter.instance_variable_get(:@collection)
raise ArgumentError, 'cfacter version 0.2.0 or later is not installed.' unless Puppet.features.cfacter?
CFacter.initialize
end
}
)
define_settings(:main,
:confdir => {
:default => nil,
:type => :directory,
:desc => "The main Puppet configuration directory. The default for this setting
is calculated based on the user. If the process is running as root or
the user that Puppet is supposed to run as, it defaults to a system
directory, but if it's running as any other user, it defaults to being
in the user's home directory.",
},
:vardir => {
:default => nil,
:type => :directory,
:owner => "service",
:group => "service",
:desc => "Where Puppet stores dynamic and growing data. The default for this
setting is calculated specially, like `confdir`_.",
},
### NOTE: this setting is usually being set to a symbol value. We don't officially have a
### setting type for that yet, but we might want to consider creating one.
:name => {
:default => nil,
:desc => "The name of the application, if we are running as one. The
default is essentially $0 without the path or `.rb`.",
}
)
define_settings(:main,
:logdir => {
:default => nil,
:type => :directory,
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory in which to store log files",
},
:log_level => {
:default => 'notice',
:type => :enum,
:values => ["debug","info","notice","warning","err","alert","emerg","crit"],
:desc => "Default logging level for messages from Puppet. Allowed values are:
* debug
* info
* notice
* warning
* err
* alert
* emerg
* crit
",
},
:disable_warnings => {
:default => [],
:type => :array,
:desc => "A comma-separated list of warning types to suppress. If large numbers
of warnings are making Puppet's logs too large or difficult to use, you
can temporarily silence them with this setting.
If you are preparing to upgrade Puppet to a new major version, you
should re-enable all warnings for a while.
Valid values for this setting are:
* `deprecations` --- disables deprecation warnings.",
:hook => proc do |value|
values = munge(value)
valid = %w[deprecations]
invalid = values - (values & valid)
if not invalid.empty?
raise ArgumentError, "Cannot disable unrecognized warning types #{invalid.inspect}. Valid values are #{valid.inspect}."
end
end
}
)
define_settings(:main,
:priority => {
:default => nil,
:type => :priority,
:desc => "The scheduling priority of the process. Valid values are 'high',
'normal', 'low', or 'idle', which are mapped to platform-specific
values. The priority can also be specified as an integer value and
will be passed as is, e.g. -5. Puppet must be running as a privileged
user in order to increase scheduling priority.",
},
:trace => {
:default => false,
:type => :boolean,
:desc => "Whether to print stack traces on some errors",
},
:profile => {
:default => false,
:type => :boolean,
:desc => "Whether to enable experimental performance profiling",
},
:autoflush => {
:default => true,
:type => :boolean,
:desc => "Whether log files should always flush to disk.",
:hook => proc { |value| Log.autoflush = value }
},
:syslogfacility => {
:default => "daemon",
:desc => "What syslog facility to use when logging to syslog.
Syslog has a fixed list of valid facilities, and you must
choose one of those; you cannot just make one up."
},
:statedir => {
:default => "$vardir/state",
:type => :directory,
:mode => "01755",
:desc => "The directory where Puppet state is stored. Generally,
this directory can be removed without causing harm (although it
might result in spurious service restarts)."
},
:rundir => {
:default => nil,
:type => :directory,
:mode => "0755",
:owner => "service",
:group => "service",
:desc => "Where Puppet PID files are kept."
},
:genconfig => {
:default => false,
:type => :boolean,
:desc => "When true, causes Puppet applications to print an example config file
to stdout and exit. The example will include descriptions of each
setting, and the current (or default) value of each setting,
incorporating any settings overridden on the CLI (with the exception
of `genconfig` itself). This setting only makes sense when specified
on the command line as `--genconfig`.",
},
:genmanifest => {
:default => false,
:type => :boolean,
:desc => "Whether to just print a manifest to stdout and exit. Only makes
sense when specified on the command line as `--genmanifest`. Takes into account arguments specified
on the CLI.",
},
:configprint => {
:default => "",
:desc => "Print the value of a specific configuration setting. If the name of a
setting is provided for this, then the value is printed and puppet
exits. Comma-separate multiple values. For a list of all values,
specify 'all'.",
},
:color => {
:default => "ansi",
:type => :string,
:desc => "Whether to use colors when logging to the console. Valid values are
`ansi` (equivalent to `true`), `html`, and `false`, which produces no color.
Defaults to false on Windows, as its console does not support ansi colors.",
},
:mkusers => {
:default => false,
:type => :boolean,
:desc => "Whether to create the necessary user and group that puppet agent will run as.",
},
:manage_internal_file_permissions => {
:default => true,
:type => :boolean,
:desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally",
},
:onetime => {
:default => false,
:type => :boolean,
:desc => "Perform one configuration run and exit, rather than spawning a long-running
daemon. This is useful for interactively running puppet agent, or
running puppet agent from cron.",
:short => 'o',
},
:path => {
:default => "none",
:desc => "The shell search path. Defaults to whatever is inherited
from the parent process.",
:call_hook => :on_define_and_write,
:hook => proc do |value|
ENV["PATH"] = "" if ENV["PATH"].nil?
ENV["PATH"] = value unless value == "none"
paths = ENV["PATH"].split(File::PATH_SEPARATOR)
Puppet::Util::Platform.default_paths.each do |path|
ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path)
end
value
end
},
:libdir => {
:type => :directory,
:default => "$vardir/lib",
:desc => "An extra search path for Puppet. This is only useful
for those files that Puppet will load on demand, and is only
guaranteed to work for those cases. In fact, the autoload
mechanism is responsible for making sure this directory
is in Ruby's search path\n",
:call_hook => :on_initialize_and_write,
:hook => proc do |value|
$LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir)
@oldlibdir = value
$LOAD_PATH << value
end
},
:ignoreimport => {
:default => false,
:type => :boolean,
:desc => "If true, allows the parser to continue without requiring
all files referenced with `import` statements to exist. This setting was primarily
designed for use with commit hooks for parse-checking.",
},
:environment => {
:default => "production",
:desc => "The environment Puppet is running in. For clients
(e.g., `puppet agent`) this determines the environment itself, which
is used to find modules and much more. For servers (i.e., `puppet master`)
this provides the default environment for nodes we know nothing about."
},
:environmentpath => {
:default => "",
:desc => "A search path for directory environments, as a list of directories
separated by the system path separator character. (The POSIX path separator
is ':', and the Windows path separator is ';'.)
This setting must have a value set to enable **directory environments.** The
recommended value is `$confdir/environments`. For more details, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
:type => :path,
},
:always_cache_features => {
:type => :boolean,
:default => false,
:desc => <<-'EOT'
Affects how we cache attempts to load Puppet 'features'. If false, then
calls to `Puppet.features.?` will always attempt to load the
feature (which can be an expensive operation) unless it has already been
loaded successfully. This makes it possible for a single agent run to,
e.g., install a package that provides the underlying capabilities for
a feature, and then later load that feature during the same run (even if
the feature had been tested earlier and had not been available).
If this setting is set to true, then features will only be checked once,
and if they are not available, the negative result is cached and returned
for all subsequent attempts to load the feature. This behavior is almost
always appropriate for the server, and can result in a significant performance
improvement for features that are checked frequently.
EOT
},
:diff_args => {
:default => lambda { default_diffargs },
:desc => "Which arguments to pass to the diff command when printing differences between
files. The command to use can be chosen with the `diff` setting.",
},
:diff => {
:default => (Puppet.features.microsoft_windows? ? "" : "diff"),
:desc => "Which diff command to use when printing differences between files. This setting
has no default value on Windows, as standard `diff` is not available, but Puppet can use many
third-party diff tools.",
},
:show_diff => {
:type => :boolean,
:default => false,
:desc => "Whether to log and report a contextual diff when files are being replaced.
This causes partial file contents to pass through Puppet's normal
logging and reporting system, so this setting should be used with
caution if you are sending Puppet's reports to an insecure
destination. This feature currently requires the `diff/lcs` Ruby
library.",
},
:daemonize => {
:type => :boolean,
:default => (Puppet.features.microsoft_windows? ? false : true),
:desc => "Whether to send the process into the background. This defaults
to true on POSIX systems, and to false on Windows (where Puppet
currently cannot daemonize).",
:short => "D",
:hook => proc do |value|
if value and Puppet.features.microsoft_windows?
raise "Cannot daemonize on Windows"
end
end
},
:maximum_uid => {
:default => 4294967290,
:desc => "The maximum allowed UID. Some platforms use negative UIDs
but then ship with tools that do not know how to handle signed ints,
so the UIDs show up as huge numbers that can then not be fed back into
the system. This is a hackish way to fail in a slightly more useful
way when that happens.",
},
:route_file => {
:default => "$confdir/routes.yaml",
:desc => "The YAML file containing indirector route configuration.",
},
:node_terminus => {
:type => :terminus,
:default => "plain",
:desc => "Where to find information about nodes.",
},
:node_cache_terminus => {
:type => :terminus,
:default => nil,
:desc => "How to store cached nodes.
Valid values are (none), 'json', 'msgpack', 'yaml' or write only yaml ('write_only_yaml').
The master application defaults to 'write_only_yaml', all others to none.",
},
:data_binding_terminus => {
:type => :terminus,
:default => "hiera",
:desc => "Where to retrive information about data.",
},
:hiera_config => {
:default => "$confdir/hiera.yaml",
:desc => "The hiera configuration file. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it.",
:type => :file,
},
:binder => {
:default => false,
:desc => "Turns the binding system on or off. This includes bindings in modules.
The binding system aggregates data from modules and other locations and makes them available for lookup.
The binding system is experimental and any or all of it may change.",
:type => :boolean,
},
:binder_config => {
:default => nil,
:desc => "The binder configuration file. Puppet reads this file on each request to configure the bindings system.
If set to nil (the default), a $confdir/binder_config.yaml is optionally loaded. If it does not exists, a default configuration
is used. If the setting :binding_config is specified, it must reference a valid and existing yaml file.",
:type => :file,
},
:catalog_terminus => {
:type => :terminus,
:default => "compiler",
:desc => "Where to get node catalogs. This is useful to change if, for instance,
you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.",
},
:catalog_cache_terminus => {
:type => :terminus,
:default => nil,
:desc => "How to store cached catalogs. Valid values are 'json', 'msgpack' and 'yaml'. The agent application defaults to 'json'."
},
:facts_terminus => {
:default => 'facter',
:desc => "The node facts terminus.",
:call_hook => :on_initialize_and_write,
:hook => proc do |value|
require 'puppet/node/facts'
# Cache to YAML if we're uploading facts away
if %w[rest inventory_service].include? value.to_s
Puppet.info "configuring the YAML fact cache because a remote terminus is active"
Puppet::Node::Facts.indirection.cache_class = :yaml
end
end
},
:inventory_terminus => {
:type => :terminus,
:default => "$facts_terminus",
:desc => "Should usually be the same as the facts terminus",
},
:default_file_terminus => {
:type => :terminus,
:default => "rest",
:desc => "The default source for files if no server is given in a
uri, e.g. puppet:///file. The default of `rest` causes the file to be
retrieved using the `server` setting. When running `apply` the default
is `file_server`, causing requests to be filled locally."
},
:httplog => {
:default => "$logdir/http.log",
:type => :file,
:owner => "root",
:mode => "0640",
:desc => "Where the puppet agent web server logs.",
},
:http_proxy_host => {
:default => "none",
:desc => "The HTTP proxy host to use for outgoing connections. Note: You
may need to use a FQDN for the server hostname when using a proxy. Environment variable
http_proxy or HTTP_PROXY will override this value",
},
:http_proxy_port => {
:default => 3128,
:desc => "The HTTP proxy port to use for outgoing connections",
},
:http_proxy_user => {
:default => "none",
:desc => "The user name for an authenticated HTTP proxy. Requires the `http_proxy_host` setting.",
},
:http_proxy_password =>{
:default => "none",
:hook => proc do |value|
if Puppet.settings[:http_proxy_password] =~ /[@!# \/]/
raise "Passwords set in the http_proxy_password setting must be valid as part of a URL, and any reserved characters must be URL-encoded. We received: #{value}"
end
end,
:desc => "The password for the user of an authenticated HTTP proxy.
Requires the `http_proxy_user` setting.
Note that passwords must be valid when used as part of a URL. If a password
contains any characters with special meanings in URLs (as specified by RFC 3986
section 2.2), they must be URL-encoded. (For example, `#` would become `%23`.)",
},
:http_keepalive_timeout => {
:default => "4s",
:type => :duration,
:desc => "The maximum amount of time a persistent HTTP connection can remain idle in the connection pool, before it is closed. This timeout should be shorter than the keepalive timeout used on the HTTP server, e.g. Apache KeepAliveTimeout directive.
#{AS_DURATION}"
},
:http_debug => {
:default => false,
:type => :boolean,
:desc => "Whether to write HTTP request and responses to stderr. This should never be used in a production environment."
},
:filetimeout => {
:default => "15s",
:type => :duration,
:desc => "The minimum time to wait between checking for updates in
configuration files. This timeout determines how quickly Puppet checks whether
a file (such as manifests or templates) has changed on disk. #{AS_DURATION}",
},
:environment_timeout => {
:default => "unlimited",
:type => :ttl,
:desc => "The time to live for a cached environment.
#{AS_DURATION}
This setting can also be set to `unlimited`, which causes the environment to
be cached until the master is restarted."
},
:queue_type => {
:default => "stomp",
:desc => "Which type of queue to use for asynchronous processing.",
},
:queue_type => {
:default => "stomp",
:desc => "Which type of queue to use for asynchronous processing.",
},
:queue_source => {
:default => "stomp://localhost:61613/",
:desc => "Which type of queue to use for asynchronous processing. If your stomp server requires
authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1",
},
:async_storeconfigs => {
:default => false,
:type => :boolean,
:desc => "Whether to use a queueing system to provide asynchronous database integration.
Requires that `puppet queue` be running.",
:hook => proc do |value|
if value
# This reconfigures the termini for Node, Facts, and Catalog
Puppet.settings.override_default(:storeconfigs, true)
# But then we modify the configuration
Puppet::Resource::Catalog.indirection.cache_class = :queue
Puppet.settings.override_default(:catalog_cache_terminus, :queue)
else
raise "Cannot disable asynchronous storeconfigs in a running process"
end
end
},
:thin_storeconfigs => {
:default => false,
:type => :boolean,
:desc =>
"Boolean; whether Puppet should store only facts and exported resources in the storeconfigs
database. This will improve the performance of exported resources with the older
`active_record` backend, but will disable external tools that search the storeconfigs database.
Thinning catalogs is generally unnecessary when using PuppetDB to store catalogs.",
:hook => proc do |value|
Puppet.settings.override_default(:storeconfigs, true) if value
end
},
:config_version => {
:default => "",
:desc => "How to determine the configuration version. By default, it will be the
time that the configuration is parsed, but you can provide a shell script to override how the
version is determined. The output of this script will be added to every log message in the
reports, allowing you to correlate changes on your hosts to the source version on the server.
Setting a global value for config_version in puppet.conf is deprecated. Please set a
per-environment value in environment.conf instead. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
:deprecated => :allowed_on_commandline,
},
:zlib => {
:default => true,
:type => :boolean,
:desc => "Boolean; whether to use the zlib library",
},
:prerun_command => {
:default => "",
:desc => "A command to run before every agent run. If this command returns a non-zero
return code, the entire Puppet run will fail.",
},
:postrun_command => {
:default => "",
:desc => "A command to run after every agent run. If this command returns a non-zero
return code, the entire Puppet run will be considered to have failed, even though it might have
performed work during the normal run.",
},
:freeze_main => {
:default => false,
:type => :boolean,
:desc => "Freezes the 'main' class, disallowing any code to be added to it. This
essentially means that you can't have any code outside of a node,
class, or definition other than in the site manifest.",
},
:stringify_facts => {
:default => true,
:type => :boolean,
:desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or
hashes as fact values. (DEPRECATED) This option will be removed in Puppet 4.0.",
},
:trusted_node_data => {
:default => false,
:type => :boolean,
:desc => "Stores trusted node data in a hash called $trusted.
When true also prevents $trusted from being overridden in any scope.",
},
:immutable_node_data => {
:default => '$trusted_node_data',
:type => :boolean,
:desc => "When true, also prevents $trusted and $facts from being overridden in any scope",
}
)
Puppet.define_settings(:module_tool,
:module_repository => {
:default => 'https://forgeapi.puppetlabs.com',
:desc => "The module repository",
},
:module_working_dir => {
:default => '$vardir/puppet-module',
:desc => "The directory into which module tool data is stored",
},
:module_skeleton_dir => {
:default => '$module_working_dir/skeleton',
:desc => "The directory which the skeleton for module tool generate is stored.",
},
:forge_authorization => {
:default => nil,
:desc => "The authorization key to connect to the Puppet Forge. Leave blank for unauthorized or license based connections",
},
:module_groups => {
:default => nil,
:desc => "Extra module groups to request from the Puppet Forge",
}
)
Puppet.define_settings(
:main,
# We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for
# manipulating naming.
:certname => {
:default => lambda { Puppet::Settings.default_certname.downcase },
:desc => "The name to use when handling certificates. When a node
requests a certificate from the CA puppet master, it uses the value of the
`certname` setting as its requested Subject CN.
This is the name used when managing a node's permissions in
[auth.conf](http://docs.puppetlabs.com/puppet/latest/reference/config_file_auth.html).
In most cases, it is also used as the node's name when matching
[node definitions](http://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html)
and requesting data from an ENC. (This can be changed with the `node_name_value`
and `node_name_fact` settings, although you should only do so if you have
a compelling reason.)
A node's certname is available in Puppet manifests as `$trusted['certname']`. (See
[Facts and Built-In Variables](http://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html)
for more details.)
* For best compatibility, you should limit the value of `certname` to
only use letters, numbers, periods, underscores, and dashes. (That is,
it should match `/\A[a-z0-9._-]+\Z/`.)
* The special value `ca` is reserved, and can't be used as the certname
for a normal node.
Defaults to the node's fully qualified domain name.",
:hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }},
:certdnsnames => {
:default => '',
:hook => proc do |value|
unless value.nil? or value == '' then
Puppet.warning < < {
:default => '',
:desc => < {
:default => "$confdir/csr_attributes.yaml",
:type => :file,
:desc => < {
:default => "$ssldir/certs",
:type => :directory,
:mode => "0755",
:owner => "service",
:group => "service",
:desc => "The certificate directory."
},
:ssldir => {
:default => "$confdir/ssl",
:type => :directory,
:mode => "0771",
:owner => "service",
:group => "service",
:desc => "Where SSL certificates are kept."
},
:publickeydir => {
:default => "$ssldir/public_keys",
:type => :directory,
:mode => "0755",
:owner => "service",
:group => "service",
:desc => "The public key directory."
},
:requestdir => {
:default => "$ssldir/certificate_requests",
:type => :directory,
:mode => "0755",
:owner => "service",
:group => "service",
:desc => "Where host certificate requests are stored."
},
:privatekeydir => {
:default => "$ssldir/private_keys",
:type => :directory,
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "The private key directory."
},
:privatedir => {
:default => "$ssldir/private",
:type => :directory,
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "Where the client stores private certificate information."
},
:passfile => {
:default => "$privatedir/password",
:type => :file,
:mode => "0640",
:owner => "service",
:group => "service",
:desc => "Where puppet agent stores the password for its private key.
Generally unused."
},
:hostcsr => {
:default => "$ssldir/csr_$certname.pem",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their certificate requests."
},
:hostcert => {
:default => "$certdir/$certname.pem",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their certificates."
},
:hostprivkey => {
:default => "$privatekeydir/$certname.pem",
:type => :file,
:mode => "0640",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their private key."
},
:hostpubkey => {
:default => "$publickeydir/$certname.pem",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where individual hosts store and look for their public key."
},
:localcacert => {
:default => "$certdir/ca.pem",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where each client stores the CA certificate."
},
:ssl_client_ca_auth => {
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Certificate authorities who issue server certificates. SSL servers will not be
considered authentic unless they possess a certificate issued by an authority
listed in this file. If this setting has no value then the Puppet master's CA
certificate (localcacert) will be used."
},
:ssl_server_ca_auth => {
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Certificate authorities who issue client certificates. SSL clients will not be
considered authentic unless they possess a certificate issued by an authority
listed in this file. If this setting has no value then the Puppet master's CA
certificate (localcacert) will be used."
},
:hostcrl => {
:default => "$ssldir/crl.pem",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "Where the host's certificate revocation list can be found.
This is distinct from the certificate authority's CRL."
},
:certificate_revocation => {
:default => true,
:type => :boolean,
:desc => "Whether certificate revocation should be supported by downloading a
Certificate Revocation List (CRL)
to all clients. If enabled, CA chaining will almost definitely not work.",
},
:certificate_expire_warning => {
:default => "60d",
:type => :duration,
:desc => "The window of time leading up to a certificate's expiration that a notification
will be logged. This applies to CA, master, and agent certificates. #{AS_DURATION}"
},
:digest_algorithm => {
:default => 'md5',
:type => :enum,
:values => ["md5", "sha256"],
:desc => 'Which digest algorithm to use for file resources and the filebucket.
Valid values are md5, sha256. Default is md5.',
}
)
define_settings(
:ca,
:ca_name => {
:default => "Puppet CA: $certname",
:desc => "The name to use the Certificate Authority certificate.",
},
:cadir => {
:default => "$ssldir/ca",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0755",
:desc => "The root directory for the certificate authority."
},
:cacert => {
:default => "$cadir/ca_crt.pem",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0644",
:desc => "The CA certificate."
},
:cakey => {
:default => "$cadir/ca_key.pem",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0640",
:desc => "The CA private key."
},
:capub => {
:default => "$cadir/ca_pub.pem",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0644",
:desc => "The CA public key."
},
:cacrl => {
:default => "$cadir/ca_crl.pem",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0644",
:desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.",
},
:caprivatedir => {
:default => "$cadir/private",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0750",
:desc => "Where the CA stores private certificate information."
},
:csrdir => {
:default => "$cadir/requests",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0755",
:desc => "Where the CA stores certificate requests"
},
:signeddir => {
:default => "$cadir/signed",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0755",
:desc => "Where the CA stores signed certificates."
},
:capass => {
:default => "$caprivatedir/ca.pass",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0640",
:desc => "Where the CA stores the password for the private key."
},
:serial => {
:default => "$cadir/serial",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0644",
:desc => "Where the serial number for certificates is stored."
},
:autosign => {
:default => "$confdir/autosign.conf",
:type => :autosign,
:desc => "Whether (and how) to autosign certificate requests. This setting
is only relevant on a puppet master acting as a certificate authority (CA).
Valid values are true (autosigns all certificate requests; not recommended),
false (disables autosigning certificates), or the absolute path to a file.
The file specified in this setting may be either a **configuration file**
or a **custom policy executable.** Puppet will automatically determine
what it is: If the Puppet user (see the `user` setting) can execute the
file, it will be treated as a policy executable; otherwise, it will be
treated as a config file.
If a custom policy executable is configured, the CA puppet master will run it
every time it receives a CSR. The executable will be passed the subject CN of the
request _as a command line argument,_ and the contents of the CSR in PEM format
_on stdin._ It should exit with a status of 0 if the cert should be autosigned
and non-zero if the cert should not be autosigned.
If a certificate request is not autosigned, it will persist for review. An admin
user can use the `puppet cert sign` command to manually sign it, or can delete
the request.
For info on autosign configuration files, see
[the guide to Puppet's config files](http://docs.puppetlabs.com/guides/configuring.html).",
},
:allow_duplicate_certs => {
:default => false,
:type => :boolean,
:desc => "Whether to allow a new certificate
request to overwrite an existing certificate.",
},
:ca_ttl => {
:default => "5y",
:type => :duration,
:desc => "The default TTL for new certificates.
#{AS_DURATION}"
},
:req_bits => {
:default => 4096,
:desc => "The bit length of the certificates.",
},
:keylength => {
:default => 4096,
:desc => "The bit length of keys.",
},
:cert_inventory => {
:default => "$cadir/inventory.txt",
:type => :file,
:mode => "0644",
:owner => "service",
:group => "service",
:desc => "The inventory file. This is a text file to which the CA writes a
complete listing of all certificates."
}
)
# Define the config default.
define_settings(:application,
:config_file_name => {
:type => :string,
:default => Puppet::Settings.default_config_file_name,
:desc => "The name of the puppet config file.",
},
:config => {
:type => :file,
:default => "$confdir/${config_file_name}",
:desc => "The configuration file for the current puppet application.",
},
:pidfile => {
:type => :file,
:default => "$rundir/${run_mode}.pid",
:desc => "The file containing the PID of a running process.
This file is intended to be used by service management frameworks
and monitoring systems to determine if a puppet process is still in
the process table.",
},
:bindaddress => {
:default => "0.0.0.0",
:desc => "The address a listening server should bind to.",
}
)
define_settings(:master,
:user => {
:default => "puppet",
:desc => "The user puppet master should run as.",
},
:group => {
:default => "puppet",
:desc => "The group puppet master should run as.",
},
:manifestdir => {
:default => "$confdir/manifests",
:type => :directory,
:desc => "Used to build the default value of the `manifest` setting. Has no other purpose.
This setting is deprecated.",
:deprecated => :completely,
},
:manifest => {
:default => "$manifestdir/site.pp",
:type => :file_or_directory,
:desc => "The entry-point manifest for puppet master. This can be one file
or a directory of manifests to be evaluated in alphabetical order. Puppet manages
this path as a directory if one exists or if the path ends with a / or \\.
Setting a global value for `manifest` in puppet.conf is deprecated. Please use
directory environments instead. If you need to use something other than the
environment's `manifests` directory as the main manifest, you can set
`manifest` in environment.conf. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
:deprecated => :allowed_on_commandline,
},
:default_manifest => {
:default => "./manifests",
:type => :string,
:desc => "The default main manifest for directory environments. Any environment that
doesn't set the `manifest` setting in its `environment.conf` file will use
this manifest.
This setting's value can be an absolute or relative path. An absolute path
will make all environments default to the same main manifest; a relative
path will allow each environment to use its own manifest, and Puppet will
resolve the path relative to each environment's main directory.
In either case, the path can point to a single file or to a directory of
manifests to be evaluated in alphabetical order.",
- :hook => proc do |value|
- uninterpolated_value = self.value(true)
- if uninterpolated_value =~ /\$environment/ || value =~ /\$environment/ then
- raise(Puppet::Settings::ValidationError,
- "You cannot interpolate '$environment' within the 'default_manifest' setting.")
- end
- end
},
:disable_per_environment_manifest => {
:default => false,
:type => :boolean,
:desc => "Whether to disallow an environment-specific main manifest. When set
to `true`, Puppet will use the manifest specified in the `default_manifest` setting
for all environments. If an environment specifies a different main manifest in its
`environment.conf` file, catalog requests for that environment will fail with an error.
This setting requires `default_manifest` to be set to an absolute path.",
:hook => proc do |value|
if value && !Pathname.new(Puppet[:default_manifest]).absolute?
raise(Puppet::Settings::ValidationError,
"The 'default_manifest' setting must be set to an absolute path when 'disable_per_environment_manifest' is true")
end
end,
},
:code => {
:default => "",
:desc => "Code to parse directly. This is essentially only used
by `puppet`, and should only be set if you're writing your own Puppet
executable.",
},
:masterhttplog => {
:default => "$logdir/masterhttp.log",
:type => :file,
:owner => "service",
:group => "service",
:mode => "0660",
:create => true,
:desc => "Where the puppet master web server saves its access log. This is
only used when running a WEBrick puppet master. When puppet master is
running under a Rack server like Passenger, that web server will have
its own logging behavior."
},
:masterport => {
:default => 8140,
:desc => "The port for puppet master traffic. For puppet master,
this is the port to listen on; for puppet agent, this is the port
to make requests on. Both applications use this setting to get the port.",
},
:node_name => {
:default => "cert",
:desc => "How the puppet master determines the client's identity
and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest,
in particular for determining which 'node' statement applies to the client.
Possible values are 'cert' (use the subject's CN in the client's
certificate) and 'facter' (use the hostname that the client
reported in its facts)",
},
:bucketdir => {
:default => "$vardir/bucket",
:type => :directory,
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "Where FileBucket files are stored."
},
:rest_authconfig => {
:default => "$confdir/auth.conf",
:type => :file,
:desc => "The configuration file that defines the rights to the different
rest indirections. This can be used as a fine-grained
authorization system for `puppet master`.",
},
:ca => {
:default => true,
:type => :boolean,
:desc => "Whether the master should function as a certificate authority.",
},
:basemodulepath => {
:default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules",
:type => :path,
:desc => "The search path for **global** modules. Should be specified as a
list of directories separated by the system path separator character. (The
POSIX path separator is ':', and the Windows path separator is ';'.)
If you are using directory environments, these are the modules that will
be used by _all_ environments. Note that the `modules` directory of the active
environment will have priority over any global directories. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html
This setting also provides the default value for the deprecated `modulepath`
setting, which is used when directory environments are disabled.",
},
:modulepath => {
:default => "$basemodulepath",
:type => :path,
:desc => "The search path for modules, as a list of directories separated by the system
path separator character. (The POSIX path separator is ':', and the
Windows path separator is ';'.)
Setting a global value for `modulepath` in puppet.conf is deprecated. Please use
directory environments instead. If you need to use something other than the
default modulepath of `:$basemodulepath`,
you can set `modulepath` in environment.conf. For more info, see
http://docs.puppetlabs.com/puppet/latest/reference/environments.html",
:deprecated => :allowed_on_commandline,
},
:ssl_client_header => {
:default => "HTTP_X_CLIENT_DN",
:desc => "The header containing an authenticated client's SSL DN.
This header must be set by the proxy to the authenticated client's SSL
DN (e.g., `/CN=puppet.puppetlabs.com`). Puppet will parse out the Common
Name (CN) from the Distinguished Name (DN) and use the value of the CN
field for authorization.
Note that the name of the HTTP header gets munged by the web server
common gateway inteface: an `HTTP_` prefix is added, dashes are converted
to underscores, and all letters are uppercased. Thus, to use the
`X-Client-DN` header, this setting should be `HTTP_X_CLIENT_DN`.",
},
:ssl_client_verify_header => {
:default => "HTTP_X_CLIENT_VERIFY",
:desc => "The header containing the status message of the client
verification. This header must be set by the proxy to 'SUCCESS' if the
client successfully authenticated, and anything else otherwise.
Note that the name of the HTTP header gets munged by the web server
common gateway inteface: an `HTTP_` prefix is added, dashes are converted
to underscores, and all letters are uppercased. Thus, to use the
`X-Client-Verify` header, this setting should be
`HTTP_X_CLIENT_VERIFY`.",
},
# To make sure this directory is created before we try to use it on the server, we need
# it to be in the server section (#1138).
:yamldir => {
:default => "$vardir/yaml",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0750",
:desc => "The directory in which YAML data is stored, usually in a subdirectory."},
:server_datadir => {
:default => "$vardir/server_data",
:type => :directory,
:owner => "service",
:group => "service",
:mode => "0750",
:desc => "The directory in which serialized data is stored, usually in a subdirectory."},
:reports => {
:default => "store",
:desc => "The list of report handlers to use. When using multiple report handlers,
their names should be comma-separated, with whitespace allowed. (For example,
`reports = http, tagmail`.)
This setting is relevant to puppet master and puppet apply. The puppet
master will call these report handlers with the reports it receives from
agent nodes, and puppet apply will call them with its own report. (In
all cases, the node applying the catalog must have `report = true`.)
See the report reference for information on the built-in report
handlers; custom report handlers can also be loaded from modules.
(Report handlers are loaded from the lib directory, at
`puppet/reports/NAME.rb`.)",
},
:reportdir => {
:default => "$vardir/reports",
:type => :directory,
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory in which to store reports. Each node gets
a separate subdirectory in this directory. This setting is only
used when the `store` report processor is enabled (see the
`reports` setting)."},
:reporturl => {
:default => "http://localhost:3000/reports/upload",
:desc => "The URL that reports should be forwarded to. This setting
is only used when the `http` report processor is enabled (see the
`reports` setting).",
},
:fileserverconfig => {
:default => "$confdir/fileserver.conf",
:type => :file,
:desc => "Where the fileserver configuration is stored.",
},
:strict_hostname_checking => {
:default => false,
:desc => "Whether to only search for the complete
hostname as it is in the certificate when searching for node information
in the catalogs.",
}
)
define_settings(:metrics,
:rrddir => {
:type => :directory,
:default => "$vardir/rrd",
:mode => "0750",
:owner => "service",
:group => "service",
:desc => "The directory where RRD database files are stored.
Directories for each reporting host will be created under
this directory."
},
:rrdinterval => {
:default => "$runinterval",
:type => :duration,
:desc => "How often RRD should expect data.
This should match how often the hosts report back to the server. #{AS_DURATION}",
}
)
define_settings(:device,
:devicedir => {
:default => "$vardir/devices",
:type => :directory,
:mode => "0750",
:desc => "The root directory of devices' $vardir.",
},
:deviceconfig => {
:default => "$confdir/device.conf",
:desc => "Path to the device config file for puppet device.",
}
)
define_settings(:agent,
:node_name_value => {
:default => "$certname",
:desc => "The explicit value used for the node name for all requests the agent
makes to the master. WARNING: This setting is mutually exclusive with
node_name_fact. Changing this setting also requires changes to the default
auth.conf configuration on the Puppet Master. Please see
http://links.puppetlabs.com/node_name_value for more information."
},
:node_name_fact => {
:default => "",
:desc => "The fact name used to determine the node name used for all requests the agent
makes to the master. WARNING: This setting is mutually exclusive with
node_name_value. Changing this setting also requires changes to the default
auth.conf configuration on the Puppet Master. Please see
http://links.puppetlabs.com/node_name_fact for more information.",
:hook => proc do |value|
if !value.empty? and Puppet[:node_name_value] != Puppet[:certname]
raise "Cannot specify both the node_name_value and node_name_fact settings"
end
end
},
:localconfig => {
:default => "$statedir/localconfig",
:type => :file,
:owner => "root",
:mode => "0660",
:desc => "Where puppet agent caches the local configuration. An
extension indicating the cache format is added automatically."},
:statefile => {
:default => "$statedir/state.yaml",
:type => :file,
:mode => "0660",
:desc => "Where puppet agent and puppet master store state associated
with the running configuration. In the case of puppet master,
this file reflects the state discovered through interacting
with clients."
},
:clientyamldir => {
:default => "$vardir/client_yaml",
:type => :directory,
:mode => "0750",
:desc => "The directory in which client-side YAML data is stored."
},
:client_datadir => {
:default => "$vardir/client_data",
:type => :directory,
:mode => "0750",
:desc => "The directory in which serialized data is stored on the client."
},
:classfile => {
:default => "$statedir/classes.txt",
:type => :file,
:owner => "root",
:mode => "0640",
:desc => "The file in which puppet agent stores a list of the classes
associated with the retrieved configuration. Can be loaded in
the separate `puppet` executable using the `--loadclasses`
option."},
:resourcefile => {
:default => "$statedir/resources.txt",
:type => :file,
:owner => "root",
:mode => "0640",
:desc => "The file in which puppet agent stores a list of the resources
associated with the retrieved configuration." },
:puppetdlog => {
:default => "$logdir/puppetd.log",
:type => :file,
:owner => "root",
:mode => "0640",
:desc => "The fallback log file. This is only used when the `--logdest` option
is not specified AND Puppet is running on an operating system where both
the POSIX syslog service and the Windows Event Log are unavailable. (Currently,
no supported operating systems match that description.)
Despite the name, both puppet agent and puppet master will use this file
as the fallback logging destination.
For control over logging destinations, see the `--logdest` command line
option in the manual pages for puppet master, puppet agent, and puppet
apply. You can see man pages by running `puppet --help`,
or read them online at http://docs.puppetlabs.com/references/latest/man/."
},
:server => {
:default => "puppet",
:desc => "The puppet master server to which the puppet agent should connect."
},
:use_srv_records => {
:default => false,
:type => :boolean,
:desc => "Whether the server will search for SRV records in DNS for the current domain.",
},
:srv_domain => {
:default => lambda { Puppet::Settings.domain_fact },
:desc => "The domain which will be queried to find the SRV records of servers to use.",
},
:ignoreschedules => {
:default => false,
:type => :boolean,
:desc => "Boolean; whether puppet agent should ignore schedules. This is useful
for initial puppet agent runs.",
},
:default_schedules => {
:default => true,
:type => :boolean,
:desc => "Boolean; whether to generate the default schedule resources. Setting this to
false is useful for keeping external report processors clean of skipped schedule resources.",
},
:puppetport => {
:default => 8139,
:desc => "Which port puppet agent listens on.",
},
:noop => {
:default => false,
:type => :boolean,
:desc => "Whether to apply catalogs in noop mode, which allows Puppet to
partially simulate a normal run. This setting affects puppet agent and
puppet apply.
When running in noop mode, Puppet will check whether each resource is in sync,
like it does when running normally. However, if a resource attribute is not in
the desired state (as declared in the catalog), Puppet will take no
action, and will instead report the changes it _would_ have made. These
simulated changes will appear in the report sent to the puppet master, or
be shown on the console if running puppet agent or puppet apply in the
foreground. The simulated changes will not send refresh events to any
subscribing or notified resources, although Puppet will log that a refresh
event _would_ have been sent.
**Important note:**
[The `noop` metaparameter](http://docs.puppetlabs.com/references/latest/metaparameter.html#noop)
allows you to apply individual resources in noop mode, and will override
the global value of the `noop` setting. This means a resource with
`noop => false` _will_ be changed if necessary, even when running puppet
agent with `noop = true` or `--noop`. (Conversely, a resource with
`noop => true` will only be simulated, even when noop mode is globally disabled.)",
},
:runinterval => {
:default => "30m",
:type => :duration,
:desc => "How often puppet agent applies the catalog.
Note that a runinterval of 0 means \"run continuously\" rather than
\"never run.\" If you want puppet agent to never run, you should start
it with the `--no-client` option. #{AS_DURATION}",
},
:listen => {
:default => false,
:type => :boolean,
:desc => "Whether puppet agent should listen for
connections. If this is true, then puppet agent will accept incoming
REST API requests, subject to the default ACLs and the ACLs set in
the `rest_authconfig` file. Puppet agent can respond usefully to
requests on the `run`, `facts`, `certificate`, and `resource` endpoints.",
},
:ca_server => {
:default => "$server",
:desc => "The server to use for certificate
authority requests. It's a separate server because it cannot
and does not need to horizontally scale.",
},
:ca_port => {
:default => "$masterport",
:desc => "The port to use for the certificate authority.",
},
:catalog_format => {
:default => "",
:desc => "(Deprecated for 'preferred_serialization_format') What format to
use to dump the catalog. Only supports 'marshal' and 'yaml'. Only
matters on the client, since it asks the server for a specific format.",
:hook => proc { |value|
if value
Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead."
Puppet.settings.override_default(:preferred_serialization_format, value)
end
}
},
:preferred_serialization_format => {
:default => "pson",
:desc => "The preferred means of serializing
ruby instances for passing over the wire. This won't guarantee that all
instances will be serialized using this method, since not all classes
can be guaranteed to support this format, but it will be used for all
classes that support it.",
},
:report_serialization_format => {
:default => "pson",
:type => :enum,
:values => ["pson", "yaml"],
:desc => "The serialization format to use when sending reports to the
`report_server`. Possible values are `pson` and `yaml`. This setting
affects puppet agent, but not puppet apply (which processes its own
reports).
This should almost always be set to `pson`. It can be temporarily set to
`yaml` to let agents using this Puppet version connect to a puppet master
running Puppet 3.0.0 through 3.2.x.
Note that this is set to 'yaml' automatically if the agent detects an
older master, so should never need to be set explicitly."
},
:legacy_query_parameter_serialization => {
:default => false,
:type => :boolean,
:desc => "The serialization format to use when sending file_metadata
query parameters. Older versions of puppet master expect certain query
parameters to be serialized as yaml, which is deprecated.
This should almost always be false. It can be temporarily set to true
to let agents using this Puppet version connect to a puppet master
running Puppet 3.0.0 through 3.2.x.
Note that this is set to true automatically if the agent detects an
older master, so should never need to be set explicitly."
},
:agent_catalog_run_lockfile => {
:default => "$statedir/agent_catalog_run.lock",
:type => :string, # (#2888) Ensure this file is not added to the settings catalog.
:desc => "A lock file to indicate that a puppet agent catalog run is currently in progress.
The file contains the pid of the process that holds the lock on the catalog run.",
},
:agent_disabled_lockfile => {
:default => "$statedir/agent_disabled.lock",
:type => :file,
:desc => "A lock file to indicate that puppet agent runs have been administratively
disabled. File contains a JSON object with state information.",
},
:usecacheonfailure => {
:default => true,
:type => :boolean,
:desc => "Whether to use the cached configuration when the remote
configuration will not compile. This option is useful for testing
new configurations, where you want to fix the broken configuration
rather than reverting to a known-good one.",
},
:use_cached_catalog => {
:default => false,
:type => :boolean,
:desc => "Whether to only use the cached catalog rather than compiling a new catalog
on every run. Puppet can be run with this enabled by default and then selectively
disabled when a recompile is desired.",
},
:ignoremissingtypes => {
:default => false,
:type => :boolean,
:desc => "Skip searching for classes and definitions that were missing during a
prior compilation. The list of missing objects is maintained per-environment and
persists until the environment is cleared or the master is restarted.",
},
:ignorecache => {
:default => false,
:type => :boolean,
:desc => "Ignore cache and always recompile the configuration. This is
useful for testing new configurations, where the local cache may in
fact be stale even if the timestamps are up to date - if the facts
change or if the server changes.",
},
:dynamicfacts => {
:default => "memorysize,memoryfree,swapsize,swapfree",
:desc => "(Deprecated) Facts that are dynamic; these facts will be ignored when deciding whether
changed facts should result in a recompile. Multiple facts should be
comma-separated.",
:hook => proc { |value|
if value
Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored."
end
}
},
:splaylimit => {
:default => "$runinterval",
:type => :duration,
:desc => "The maximum time to delay before runs. Defaults to being the same as the
run interval. #{AS_DURATION}",
},
:splay => {
:default => false,
:type => :boolean,
:desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before
a run.",
},
:clientbucketdir => {
:default => "$vardir/clientbucket",
:type => :directory,
:mode => "0750",
:desc => "Where FileBucket files are stored locally."
},
:configtimeout => {
:default => "2m",
:type => :duration,
:desc => "How long the client should wait for the configuration to be retrieved
before considering it a failure. This can help reduce flapping if too
many clients contact the server at one time. #{AS_DURATION}",
},
:report_server => {
:default => "$server",
:desc => "The server to send transaction reports to.",
},
:report_port => {
:default => "$masterport",
:desc => "The port to communicate with the report_server.",
},
:inventory_server => {
:default => "$server",
:desc => "The server to send facts to.",
},
:inventory_port => {
:default => "$masterport",
:desc => "The port to communicate with the inventory_server.",
},
:report => {
:default => true,
:type => :boolean,
:desc => "Whether to send reports after every transaction.",
},
:lastrunfile => {
:default => "$statedir/last_run_summary.yaml",
:type => :file,
:mode => "0644",
:desc => "Where puppet agent stores the last run report summary in yaml format."
},
:lastrunreport => {
:default => "$statedir/last_run_report.yaml",
:type => :file,
:mode => "0640",
:desc => "Where puppet agent stores the last run report in yaml format."
},
:graph => {
:default => false,
:type => :boolean,
:desc => "Whether to create dot graph files for the different
configuration graphs. These dot files can be interpreted by tools
like OmniGraffle or dot (which is part of ImageMagick).",
},
:graphdir => {
:default => "$statedir/graphs",
:type => :directory,
:desc => "Where to store dot-outputted graphs.",
},
:http_compression => {
:default => false,
:type => :boolean,
:desc => "Allow http compression in REST communication with the master.
This setting might improve performance for agent -> master
communications over slow WANs. Your puppet master needs to support
compression (usually by activating some settings in a reverse-proxy in
front of the puppet master, which rules out webrick). It is harmless to
activate this settings if your master doesn't support compression, but
if it supports it, this setting might reduce performance on high-speed LANs.",
},
:waitforcert => {
:default => "2m",
:type => :duration,
:desc => "How frequently puppet agent should ask for a signed certificate.
When starting for the first time, puppet agent will submit a certificate
signing request (CSR) to the server named in the `ca_server` setting
(usually the puppet master); this may be autosigned, or may need to be
approved by a human, depending on the CA server's configuration.
Puppet agent cannot apply configurations until its approved certificate is
available. Since the certificate may or may not be available immediately,
puppet agent will repeatedly try to fetch it at this interval. You can
turn off waiting for certificates by specifying a time of 0, in which case
puppet agent will exit if it cannot get a cert.
#{AS_DURATION}",
},
:ordering => {
:type => :enum,
:values => ["manifest", "title-hash", "random"],
:default => "manifest",
:desc => "How unrelated resources should be ordered when applying a catalog.
Allowed values are `title-hash`, `manifest`, and `random`. This
setting affects puppet agent and puppet apply, but not puppet master.
* `title-hash` (the default) will order resources randomly, but will use
the same order across runs and across nodes.
* `manifest` will use the order in which the resources were declared in
their manifest files.
* `random` will order resources randomly and change their order with each
run. This can work like a fuzzer for shaking out undeclared dependencies.
Regardless of this setting's value, Puppet will always obey explicit
dependencies set with the before/require/notify/subscribe metaparameters
and the `->`/`~>` chaining arrows; this setting only affects the relative
ordering of _unrelated_ resources."
}
)
define_settings(:inspect,
:archive_files => {
:type => :boolean,
:default => false,
:desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.",
},
:archive_file_server => {
:default => "$server",
:desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.",
}
)
# Plugin information.
define_settings(
:main,
:plugindest => {
:type => :directory,
:default => "$libdir",
:desc => "Where Puppet should store plugins that it pulls down from the central
server.",
},
:pluginsource => {
:default => "puppet://$server/plugins",
:desc => "From where to retrieve plugins. The standard Puppet `file` type
is used for retrieval, so anything that is a valid file source can
be used here.",
},
:pluginfactdest => {
:type => :directory,
:default => "$vardir/facts.d",
:desc => "Where Puppet should store external facts that are being handled by pluginsync",
},
:pluginfactsource => {
:default => "puppet://$server/pluginfacts",
:desc => "Where to retrieve external facts for pluginsync",
},
:pluginsync => {
:default => true,
:type => :boolean,
:desc => "Whether plugins should be synced with the central server.",
},
:pluginsignore => {
:default => ".svn CVS .git",
:desc => "What files to ignore when pulling down plugins.",
}
)
# Central fact information.
define_settings(
:main,
:factpath => {
:type => :path,
:default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts",
:desc => "Where Puppet should look for facts. Multiple directories should
be separated by the system path separator character. (The POSIX path
separator is ':', and the Windows path separator is ';'.)",
:call_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter.
:hook => proc do |value|
paths = value.split(File::PATH_SEPARATOR)
Facter.search(*paths)
end
}
)
define_settings(
:tagmail,
:tagmap => {
:default => "$confdir/tagmail.conf",
:desc => "The mapping between reporting tags and email addresses.",
},
:sendmail => {
:default => which('sendmail') || '',
:desc => "Where to find the sendmail binary with which to send email.",
},
:reportfrom => {
:default => lambda { "report@#{Puppet::Settings.default_certname.downcase}" },
:desc => "The 'from' email address for the reports.",
},
:smtpserver => {
:default => "none",
:desc => "The server through which to send email reports.",
},
:smtpport => {
:default => 25,
:desc => "The TCP port through which to send email reports.",
},
:smtphelo => {
:default => lambda { Facter.value 'fqdn' },
:desc => "The name by which we identify ourselves in SMTP HELO for reports.
If you send to a smtpserver which does strict HELO checking (as with Postfix's
`smtpd_helo_restrictions` access controls), you may need to ensure this resolves.",
}
)
define_settings(
:rails,
:dblocation => {
:default => "$statedir/clientconfigs.sqlite3",
:type => :file,
:mode => "0660",
:owner => "service",
:group => "service",
:desc => "The sqlite database file. #{STORECONFIGS_ONLY}"
},
:dbadapter => {
:default => "sqlite3",
:desc => "The type of database to use. #{STORECONFIGS_ONLY}",
},
:dbmigrate => {
:default => false,
:type => :boolean,
:desc => "Whether to automatically migrate the database. #{STORECONFIGS_ONLY}",
},
:dbname => {
:default => "puppet",
:desc => "The name of the database to use. #{STORECONFIGS_ONLY}",
},
:dbserver => {
:default => "localhost",
:desc => "The database server for caching. Only
used when networked databases are used.",
},
:dbport => {
:default => "",
:desc => "The database password for caching. Only
used when networked databases are used. #{STORECONFIGS_ONLY}",
},
:dbuser => {
:default => "puppet",
:desc => "The database user for caching. Only
used when networked databases are used. #{STORECONFIGS_ONLY}",
},
:dbpassword => {
:default => "puppet",
:desc => "The database password for caching. Only
used when networked databases are used. #{STORECONFIGS_ONLY}",
},
:dbconnections => {
:default => '',
:desc => "The number of database connections for networked
databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}",
},
:dbsocket => {
:default => "",
:desc => "The database socket location. Only used when networked
databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}",
},
:railslog => {
:default => "$logdir/rails.log",
:type => :file,
:mode => "0600",
:owner => "service",
:group => "service",
:desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}"
},
:rails_loglevel => {
:default => "info",
:desc => "The log level for Rails connections. The value must be
a valid log level within Rails. Production environments normally use `info`
and other environments normally use `debug`. #{STORECONFIGS_ONLY}",
}
)
define_settings(
:couchdb,
:couchdb_url => {
:default => "http://127.0.0.1:5984/puppet",
:desc => "The url where the puppet couchdb database will be created.
Only used when `facts_terminus` is set to `couch`.",
}
)
define_settings(
:transaction,
:tags => {
:default => "",
:desc => "Tags to use to find resources. If this is set, then
only resources tagged with the specified tags will be applied.
Values must be comma-separated.",
},
:evaltrace => {
:default => false,
:type => :boolean,
:desc => "Whether each resource should log when it is
being evaluated. This allows you to interactively see exactly
what is being done.",
},
:summarize => {
:default => false,
:type => :boolean,
:desc => "Whether to print a transaction summary.",
}
)
define_settings(
:main,
:external_nodes => {
:default => "none",
:desc => "An external command that can produce node information. The command's output
must be a YAML dump of a hash, and that hash must have a `classes` key and/or
a `parameters` key, where `classes` is an array or hash and
`parameters` is a hash. For unknown nodes, the command should
exit with a non-zero exit code.
This command makes it straightforward to store your node mapping
information in other data sources like databases.",
}
)
define_settings(
:ldap,
:ldapssl => {
:default => false,
:type => :boolean,
:desc => "Whether SSL should be used when searching for nodes.
Defaults to false because SSL usually requires certificates
to be set up on the client side.",
},
:ldaptls => {
:default => false,
:type => :boolean,
:desc => "Whether TLS should be used when searching for nodes.
Defaults to false because TLS usually requires certificates
to be set up on the client side.",
},
:ldapserver => {
:default => "ldap",
:desc => "The LDAP server. Only used if `node_terminus` is set to `ldap`.",
},
:ldapport => {
:default => 389,
:desc => "The LDAP port. Only used if `node_terminus` is set to `ldap`.",
},
:ldapstring => {
:default => "(&(objectclass=puppetClient)(cn=%s))",
:desc => "The search string used to find an LDAP node.",
},
:ldapclassattrs => {
:default => "puppetclass",
:desc => "The LDAP attributes to use to define Puppet classes. Values
should be comma-separated.",
},
:ldapstackedattrs => {
:default => "puppetvar",
:desc => "The LDAP attributes that should be stacked to arrays by adding
the values in all hierarchy elements of the tree. Values
should be comma-separated.",
},
:ldapattrs => {
:default => "all",
:desc => "The LDAP attributes to include when querying LDAP for nodes. All
returned attributes are set as variables in the top-level scope.
Multiple values should be comma-separated. The value 'all' returns
all attributes.",
},
:ldapparentattr => {
:default => "parentnode",
:desc => "The attribute to use to define the parent node.",
},
:ldapuser => {
:default => "",
:desc => "The user to use to connect to LDAP. Must be specified as a
full DN.",
},
:ldappassword => {
:default => "",
:desc => "The password to use to connect to LDAP.",
},
:ldapbase => {
:default => "",
:desc => "The search base for LDAP searches. It's impossible to provide
a meaningful default here, although the LDAP libraries might
have one already set. Generally, it should be the 'ou=Hosts'
branch under your main directory.",
}
)
define_settings(:master,
:storeconfigs => {
:default => false,
:type => :boolean,
:desc => "Whether to store each client's configuration, including catalogs, facts,
and related data. This also enables the import and export of resources in
the Puppet language - a mechanism for exchange resources between nodes.
By default this uses ActiveRecord and an SQL database to store and query
the data; this, in turn, will depend on Rails being available.
You can adjust the backend using the storeconfigs_backend setting.",
# Call our hook with the default value, so we always get the libdir set.
:call_hook => :on_initialize_and_write,
:hook => proc do |value|
require 'puppet/node'
require 'puppet/node/facts'
if value
if not Puppet.settings[:async_storeconfigs]
Puppet::Resource::Catalog.indirection.cache_class = :store_configs
Puppet.settings.override_default(:catalog_cache_terminus, :store_configs)
end
Puppet::Node::Facts.indirection.cache_class = :store_configs
Puppet::Resource.indirection.terminus_class = :store_configs
end
end
},
:storeconfigs_backend => {
:type => :terminus,
:default => "active_record",
:desc => "Configure the backend terminus used for StoreConfigs.
By default, this uses the ActiveRecord store, which directly talks to the
database from within the Puppet Master process."
}
)
define_settings(:parser,
:templatedir => {
:default => "$vardir/templates",
:type => :directory,
:desc => "Where Puppet looks for template files. Can be a list of colon-separated
directories.
This setting is deprecated. Please put your templates in modules instead.",
:deprecated => :completely,
},
:allow_variables_with_dashes => {
:default => false,
:desc => <<-'EOT'
Permit hyphens (`-`) in variable names and issue deprecation warnings about
them. This setting **should always be `false`;** setting it to `true`
will cause subtle and wide-ranging bugs. It will be removed in a future version.
Hyphenated variables caused major problems in the language, but were allowed
between Puppet 2.7.3 and 2.7.14. If you used them during this window, we
apologize for the inconvenience --- you can temporarily set this to `true`
in order to upgrade, and can rename your variables at your leisure. Please
revert it to `false` after you have renamed all affected variables.
EOT
},
:parser => {
:default => "current",
:desc => <<-'EOT'
Selects the parser to use for parsing puppet manifests (in puppet DSL
language/'.pp' files). Available choices are `current` (the default)
and `future`.
The `current` parser means that the released version of the parser should
be used.
The `future` parser is a "time travel to the future" allowing early
exposure to new language features. What these features are will vary from
release to release and they may be invididually configurable.
Available Since Puppet 3.2.
EOT
},
:max_errors => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation errors in case
multiple errors have been detected. A value of 0 is the same as a value of 1; a
minimum of one error is always raised. The count is per manifest.
EOT
},
:max_warnings => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation warnings in
case multiple warnings have been detected. A value of 0 blocks logging of
warnings. The count is per manifest.
EOT
},
:max_deprecations => {
:default => 10,
:desc => <<-'EOT'
Sets the max number of logged/displayed parser validation deprecation
warnings in case multiple deprecation warnings have been detected. A value of 0
blocks the logging of deprecation warnings. The count is per manifest.
EOT
},
:strict_variables => {
:default => false,
:type => :boolean,
:desc => <<-'EOT'
Makes the parser raise errors when referencing unknown variables. (This does not affect
referencing variables that are explicitly set to undef).
EOT
}
)
define_settings(:puppetdoc,
:document_all => {
:default => false,
:type => :boolean,
:desc => "Whether to document all resources when using `puppet doc` to
generate manifest documentation.",
}
)
end
diff --git a/lib/puppet/module_tool/applications/unpacker.rb b/lib/puppet/module_tool/applications/unpacker.rb
index 8ffef3d64..1c609f200 100644
--- a/lib/puppet/module_tool/applications/unpacker.rb
+++ b/lib/puppet/module_tool/applications/unpacker.rb
@@ -1,100 +1,100 @@
require 'pathname'
require 'tmpdir'
require 'json'
require 'puppet/file_system'
module Puppet::ModuleTool
module Applications
class Unpacker < Application
def self.unpack(filename, target)
app = self.new(filename, :target_dir => target)
app.unpack
app.sanity_check
app.move_into(target)
end
def self.harmonize_ownership(source, target)
unless Puppet.features.microsoft_windows?
source = Pathname.new(source) unless source.respond_to?(:stat)
target = Pathname.new(target) unless target.respond_to?(:stat)
FileUtils.chown_R(source.stat.uid, source.stat.gid, target)
end
end
def initialize(filename, options = {})
@filename = Pathname.new(filename)
super(options)
@module_path = Pathname(options[:target_dir])
end
def run
unpack
sanity_check
module_dir = @module_path + module_name
move_into(module_dir)
# Return the Pathname object representing the directory where the
# module release archive was unpacked the to.
return module_dir
end
# @api private
# Error on symlinks and other junk
def sanity_check
symlinks = Dir.glob("#{tmpdir}/**/*", File::FNM_DOTMATCH).map { |f| Pathname.new(f) }.select {|p| Puppet::FileSystem.symlink? p}
tmpdirpath = Pathname.new tmpdir
symlinks.each do |s|
- Puppet.warning "Symlinks in modules are unsupported. Please investigate symlink #{s.relative_path_from tmpdirpath}->#{s.realpath.relative_path_from tmpdirpath}."
+ Puppet.warning "Symlinks in modules are unsupported. Please investigate symlink #{s.relative_path_from tmpdirpath}->#{Puppet::FileSystem.readlink(s)}."
end
end
# @api private
def unpack
begin
Puppet::ModuleTool::Tar.instance.unpack(@filename.to_s, tmpdir, [@module_path.stat.uid, @module_path.stat.gid].join(':'))
rescue Puppet::ExecutionFailure => e
raise RuntimeError, "Could not extract contents of module archive: #{e.message}"
end
end
# @api private
def root_dir
return @root_dir if @root_dir
# Grab the first directory containing a metadata.json file
metadata_file = Dir["#{tmpdir}/**/metadata.json"].sort_by(&:length)[0]
if metadata_file
@root_dir = Pathname.new(metadata_file).dirname
else
raise "No valid metadata.json found!"
end
end
# @api private
def module_name
metadata = JSON.parse((root_dir + 'metadata.json').read)
name = metadata['name'][/-(.*)/, 1]
end
# @api private
def move_into(dir)
dir = Pathname.new(dir)
dir.rmtree if dir.exist?
FileUtils.mv(root_dir, dir)
ensure
FileUtils.rmtree(tmpdir)
end
# Obtain a suitable temporary path for unpacking tarballs
#
# @api private
# @return [String] path to temporary unpacking location
def tmpdir
@dir ||= Dir.mktmpdir('tmp-unpacker', Puppet::Forge::Cache.base_path)
end
end
end
end
diff --git a/lib/puppet/pops/loader/loader_paths.rb b/lib/puppet/pops/loader/loader_paths.rb
index 09bb7e5b0..505887915 100644
--- a/lib/puppet/pops/loader/loader_paths.rb
+++ b/lib/puppet/pops/loader/loader_paths.rb
@@ -1,118 +1,118 @@
# LoaderPaths
# ===
# The central loader knowledge about paths, what they represent and how to instantiate from them.
# Contains helpers (*smart paths*) to deal with lazy resolution of paths.
#
# TODO: Currently only supports loading of functions (3 kinds)
#
module Puppet::Pops::Loader::LoaderPaths
# Returns an array of SmartPath, each instantiated with a reference to the given loader (for root path resolution
# and existence checks). The smart paths in the array appear in precedence order. The returned array may be
# mutated.
#
def self.relative_paths_for_type(type, loader)
result =
case type
when :function
[FunctionPath4x.new(loader)]
else
# unknown types, simply produce an empty result; no paths to check, nothing to find... move along...
[]
end
result
end
# # DO NOT REMOVE YET. needed later? when there is the need to decamel a classname
# def de_camel(fq_name)
# fq_name.to_s.gsub(/::/, '/').
# gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
# gsub(/([a-z\d])([A-Z])/,'\1_\2').
# tr("-", "_").
# downcase
# end
class SmartPath
# Generic path, in the sense of "if there are any entities of this kind to load, where are they?"
attr_reader :generic_path
# Creates SmartPath for the given loader (loader knows how to check for existence etc.)
def initialize(loader)
@loader = loader
end
def generic_path()
return @generic_path unless @generic_path.nil?
root_path = @loader.path
@generic_path = (root_path.nil? ? relative_path : File.join(root_path, relative_path))
end
# Effective path is the generic path + the name part(s) + extension.
#
def effective_path(typed_name, start_index_in_name)
"#{File.join(generic_path, typed_name.name_parts)}#{extension}"
end
def relative_path()
raise NotImplementedError.new
end
def instantiator()
raise NotImplementedError.new
end
end
class RubySmartPath < SmartPath
def extension
".rb"
end
# Duplication of extension information, but avoids one call
def effective_path(typed_name, start_index_in_name)
"#{File.join(generic_path, typed_name.name_parts)}.rb"
end
end
class FunctionPath4x < RubySmartPath
- FUNCTION_PATH_4X = File.join('lib', 'puppet', 'functions')
+ FUNCTION_PATH_4X = File.join('puppet', 'functions')
def relative_path
FUNCTION_PATH_4X
end
def instantiator()
Puppet::Pops::Loader::RubyFunctionInstantiator
end
end
# SmartPaths
# ===
# Holds effective SmartPath instances per type
#
class SmartPaths
def initialize(path_based_loader)
@loader = path_based_loader
@smart_paths = {}
end
# Ensures that the paths for the type have been probed and pruned to what is existing relative to
# the given root.
#
# @param type [Symbol] the entity type to load
# @return [Array] array of effective paths for type (may be empty)
#
def effective_paths(type)
smart_paths = @smart_paths
loader = @loader
unless effective_paths = smart_paths[type]
# type not yet processed, does the various directories for the type exist ?
# Get the relative dirs for the type
paths_for_type = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(type, loader)
# Check which directories exist in the loader's content/index
effective_paths = smart_paths[type] = paths_for_type.select { |sp| loader.meaningful_to_search?(sp) }
end
effective_paths
end
end
end
diff --git a/lib/puppet/pops/loader/module_loaders.rb b/lib/puppet/pops/loader/module_loaders.rb
index 40ff5141d..3edd51561 100644
--- a/lib/puppet/pops/loader/module_loaders.rb
+++ b/lib/puppet/pops/loader/module_loaders.rb
@@ -1,242 +1,256 @@
# =ModuleLoaders
# A ModuleLoader loads items from a single module.
# The ModuleLoaders (ruby) module contains various such loaders. There is currently one concrete
# implementation, ModuleLoaders::FileBased that loads content from the file system.
# Other implementations can be created - if they are based on name to path mapping where the path
# is relative to a root path, they can derive the base behavior from the ModuleLoaders::AbstractPathBasedModuleLoader class.
#
# Examples of such extensions could be a zip/jar/compressed file base loader.
#
# Notably, a ModuleLoader does not configure itself - it is given the information it needs (the root, its name etc.)
# Logic higher up in the loader hierarchy of things makes decisions based on the "shape of modules", and "available
# modules" to determine which module loader to use for each individual module. (There could be differences in
# internal layout etc.)
#
# A module loader is also not aware of the mapping of name to relative paths - this is performed by the
# included module Puppet::Pops::Loader::PathBasedInstantatorConfig which knows about the map from type/name to
# relative path, and the logic that can instantiate what is expected to be found in the content of that path.
#
# @api private
#
module Puppet::Pops::Loader::ModuleLoaders
+ def self.system_loader_from(parent_loader, loaders)
+ # Puppet system may be installed in a fixed location via RPM, installed as a Gem, via source etc.
+ # The only way to find this across the different ways puppet can be installed is
+ # to search up the path from this source file's __FILE__ location until it finds the base of
+ # puppet.
+ #
+ puppet_lib = File.join(File.dirname(__FILE__), '../../..')
+ Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader,
+ loaders,
+ nil,
+ puppet_lib,
+ 'puppet_system')
+ end
+
+ def self.module_loader_from(parent_loader, loaders, module_name, module_path)
+ Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader,
+ loaders,
+ module_name,
+ File.join(module_path, 'lib'),
+ module_name)
+ end
+
class AbstractPathBasedModuleLoader < Puppet::Pops::Loader::BaseLoader
# The name of the module, or nil, if this is a global "component"
attr_reader :module_name
# The path to the location of the module/component - semantics determined by subclass
attr_reader :path
# A map of type to smart-paths that help with minimizing the number of paths to scan
attr_reader :smart_paths
# A Module Loader has a private loader, it is lazily obtained on request to provide the visibility
# for entities contained in the module. Since a ModuleLoader also represents an environment and it is
# created a different way, this loader can be set explicitly by the loaders bootstrap logic.
#
# @api private
attr_accessor :private_loader
# Initialize a kind of ModuleLoader for one module
# @param parent_loader [Puppet::Pops::Loader] loader with higher priority
# @param module_name [String] the name of the module (non qualified name), may be nil for a global "component"
# @param path [String] the path to the root of the module (semantics defined by subclass)
# @param loader_name [String] a name that is used for human identification (useful when module_name is nil)
#
def initialize(parent_loader, loaders, module_name, path, loader_name)
super parent_loader, loader_name
- # Irrespective of the path referencing a directory or file, the path must exist.
- unless Puppet::FileSystem.exist?(path)
- raise ArgumentError, "The given path '#{path}' does not exist!"
- end
-
@module_name = module_name
@path = path
@smart_paths = Puppet::Pops::Loader::LoaderPaths::SmartPaths.new(self)
@loaders = loaders
end
# Finds typed/named entity in this module
# @param typed_name [Puppet::Pops::Loader::TypedName] the type/name to find
# @return [Puppet::Pops::Loader::Loader::NamedEntry, nil found/created entry, or nil if not found
#
def find(typed_name)
# Assume it is a global name, and that all parts of the name should be used when looking up
name_part_index = 0
name_parts = typed_name.name_parts
# Certain types and names can be disqualified up front
if name_parts.size > 1
# The name is in a name space.
# Then entity cannot possible be in this module unless the name starts with the module name.
# Note: If "module" represents a "global component", the module_name is nil and cannot match which is
# ok since such a "module" cannot have namespaced content).
#
return nil unless name_parts[0] == module_name
# Skip the first part of the name when computing the path since the path already contains the name of the
# module
name_part_index = 1
else
# The name is in the global name space.
# The only globally name-spaced elements that may be loaded from modules are functions and resource types
case typed_name.type
when :function
when :resource_type
else
# anything else cannot possibly be in this module
# TODO: should not be allowed anyway... may have to revisit this decision
return nil
end
end
# Get the paths that actually exist in this module (they are lazily processed once and cached).
# The result is an array (that may be empty).
# Find the file to instantiate, and instantiate the entity if file is found
origin = nil
if (smart_path = smart_paths.effective_paths(typed_name.type).find do |sp|
origin = sp.effective_path(typed_name, name_part_index)
existing_path(origin)
end)
value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin))
# cache the entry and return it
set_entry(typed_name, value, origin)
else
nil
end
end
# Abstract method that subclasses override that checks if it is meaningful to search using a generic smart path.
# This optimization is performed to not be tricked into searching an empty directory over and over again.
# The implementation may perform a deep search for file content other than directories and cache this in
# and index. It is guaranteed that a call to meaningful_to_search? takes place before checking any other
# path with relative_path_exists?.
#
# This optimization exists because many modules have been created from a template and they have
# empty directories for functions, types, etc. (It is also the place to create a cached index of the content).
#
# @param smart_path [String] a path relative to the module's root
# @return [Boolean] true if there is content in the directory appointed by the relative path
#
def meaningful_to_search?(smart_path)
raise NotImplementedError.new
end
# Abstract method that subclasses override to answer if the given relative path exists, and if so returns that path
#
# @param resolved_path [String] a path resolved by a smart path against the loader's root (if it has one)
# @return [Boolean] true if the file exists
#
def existing_path(resolved_path)
raise NotImplementedError.new
end
# Abstract method that subclasses override to produce the content of the effective path.
# It should either succeed and return a String or fail with an exception.
#
# @param effective_path [String] a path as resolved by a smart path
# @return [String] the content of the file
#
def get_contents(effective_path)
raise NotImplementedError.new
end
# Abstract method that subclasses override to produce a source reference String used to identify the
# system resource (resource in the URI sense).
#
# @param relative_path [String] a path relative to the module's root
# @return [String] a reference to the source file (in file system, zip file, or elsewhere).
#
def get_source_ref(relative_path)
raise NotImplementedError.new
end
# Produces the private loader for the module. If this module is not already resolved, this will trigger resolution
#
def private_loader
@private_loader ||= @loaders.private_loader_for_module(module_name)
end
end
# @api private
#
class FileBased < AbstractPathBasedModuleLoader
attr_reader :smart_paths
attr_reader :path_index
# Create a kind of ModuleLoader for one module (Puppet Module, or module like)
#
# @param parent_loader [Puppet::Pops::Loader::Loader] typically the loader for the environment or root
# @param module_name [String] the name of the module (non qualified name), may be nil for "modules" only containing globals
# @param path [String] the path to the root of the module (semantics defined by subclass)
# @param loader_name [String] a name that identifies the loader
#
def initialize(parent_loader, loaders, module_name, path, loader_name)
super
- unless Puppet::FileSystem.directory?(path)
- raise ArgumentError, "The given module root path '#{path}' is not a directory (required for file system based module path entry)"
- end
@path_index = Set.new()
end
def existing_path(effective_path)
# Optimized, checks index instead of visiting file system
@path_index.include?(effective_path) ? effective_path : nil
end
def meaningful_to_search?(smart_path)
! add_to_index(smart_path).empty?
end
def to_s()
"(ModuleLoader::FileBased '#{loader_name()}' '#{module_name()}')"
end
def add_to_index(smart_path)
found = Dir.glob(File.join(smart_path.generic_path, '**', "*#{smart_path.extension}"))
@path_index.merge(found)
found
end
def get_contents(effective_path)
Puppet::FileSystem.read(effective_path)
end
end
# Loads from a gem specified as a URI, gem://gemname/optional/path/in/gem, or just a String gemname.
# The source reference (shown in errors etc.) is the expanded path of the gem as this is believed to be more
# helpful - given the location it should be quite obvious which gem it is, without the location, the user would
# need to go on a hunt for where the file actually is located.
#
# TODO: How does this get instantiated? Does the gemname refelect the name of the module (the namespace)
# or is that specified a different way? Can a gem be the container of multiple modules?
#
# @api private
#
class GemBased < FileBased
include Puppet::Pops::Loader::GemSupport
attr_reader :gem_ref
# Create a kind of ModuleLoader for one module
# The parameters are:
# * parent_loader - typically the loader for the root
# * module_name - the name of the module (non qualified name)
# * gem_ref - [URI, String] gem reference to the root of the module (URI, gem://gemname/optional/path/in/gem), or
# just the gem's name as a String.
#
def initialize(parent_loader, loaders, module_name, gem_ref, loader_name)
@gem_ref = gem_ref
super parent_loader, loaders, module_name, gem_dir(gem_ref), loader_name
end
def to_s()
"(ModuleLoader::GemBased '#{loader_name()}' '#{@gem_ref}' [#{module_name()}])"
end
end
end
diff --git a/lib/puppet/pops/loaders.rb b/lib/puppet/pops/loaders.rb
index ed113291a..f6a147fe1 100644
--- a/lib/puppet/pops/loaders.rb
+++ b/lib/puppet/pops/loaders.rb
@@ -1,240 +1,231 @@
class Puppet::Pops::Loaders
class LoaderError < Puppet::Error; end
attr_reader :static_loader
attr_reader :puppet_system_loader
attr_reader :public_environment_loader
attr_reader :private_environment_loader
def initialize(environment)
# The static loader can only be changed after a reboot
@@static_loader ||= Puppet::Pops::Loader::StaticLoader.new()
# Create the set of loaders
# 1. Puppet, loads from the "running" puppet - i.e. bundled functions, types, extension points and extensions
# Does not change without rebooting the service running puppet.
#
@@puppet_system_loader ||= create_puppet_system_loader()
# 2. Environment loader - i.e. what is bound across the environment, may change for each setup
# TODO: loaders need to work when also running in an agent doing catalog application. There is no
# concept of environment the same way as when running as a master (except when doing apply).
# The creation mechanisms should probably differ between the two.
#
@private_environment_loader = create_environment_loader(environment)
# 3. module loaders are set up from the create_environment_loader, they register themselves
end
# Clears the cached static and puppet_system loaders (to enable testing)
#
def self.clear
@@static_loader = nil
@@puppet_system_loader = nil
end
def static_loader
@@static_loader
end
def puppet_system_loader
@@puppet_system_loader
end
def public_loader_for_module(module_name)
md = @module_resolver[module_name] || (return nil)
# Note, this loader is not resolved until there is interest in the visibility of entities from the
# perspective of something contained in the module. (Many request may pass through a module loader
# without it loading anything.
# See {#private_loader_for_module}, and not in {#configure_loaders_for_modules}
md.public_loader
end
def private_loader_for_module(module_name)
md = @module_resolver[module_name] || (return nil)
# Since there is interest in the visibility from the perspective of entities contained in the
# module, it must be resolved (to provide this visibility).
# See {#configure_loaders_for_modules}
unless md.resolved?
@module_resolver.resolve(md)
end
md.private_loader
end
private
def create_puppet_system_loader()
- module_name = nil
- loader_name = 'puppet_system'
-
- # Puppet system may be installed in a fixed location via RPM, installed as a Gem, via source etc.
- # The only way to find this across the different ways puppet can be installed is
- # to search up the path from this source file's __FILE__ location until it finds the parent of
- # lib/puppet... e.g.. dirname(__FILE__)/../../.. (i.e. /lib/puppet/pops/loaders.rb).
- #
- puppet_lib = File.join(File.dirname(__FILE__), '../../..')
- Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, self, module_name, puppet_lib, loader_name)
+ Puppet::Pops::Loader::ModuleLoaders.system_loader_from(static_loader, self)
end
def create_environment_loader(environment)
# This defines where to start parsing/evaluating - the "initial import" (to use 3x terminology)
# Is either a reference to a single .pp file, or a directory of manifests. If the environment becomes
# a module and can hold functions, types etc. then these are available across all other modules without
# them declaring this dependency - it is however valuable to be able to treat it the same way
# bindings and other such system related configuration.
# This is further complicated by the many options available:
# - The environment may not have a directory, the code comes from one appointed 'manifest' (site.pp)
# - The environment may have a directory and also point to a 'manifest'
# - The code to run may be set in settings (code)
# Further complication is that there is nothing specifying what the visibility is into
# available modules. (3x is everyone sees everything).
# Puppet binder currently reads confdir/bindings - that is bad, it should be using the new environment support.
# The environment is not a namespace, so give it a nil "module_name"
module_name = nil
loader_name = "environment:#{environment.name}"
loader = Puppet::Pops::Loader::SimpleEnvironmentLoader.new(puppet_system_loader, loader_name)
# An environment has a module path even if it has a null loader
configure_loaders_for_modules(loader, environment)
# modules should see this loader
@public_environment_loader = loader
# Code in the environment gets to see all modules (since there is no metadata for the environment)
# but since this is not given to the module loaders, they can not load global code (since they can not
# have prior knowledge about this
loader = Puppet::Pops::Loader::DependencyLoader.new(loader, "environment", @module_resolver.all_module_loaders())
# The module loader gets the private loader via a lazy operation to look up the module's private loader.
# This does not work for an environment since it is not resolved the same way.
# TODO: The EnvironmentLoader could be a specialized loader instead of using a ModuleLoader to do the work.
# This is subject to future design - an Environment may move more in the direction of a Module.
@public_environment_loader.private_loader = loader
loader
end
def configure_loaders_for_modules(parent_loader, environment)
@module_resolver = mr = ModuleResolver.new()
environment.modules.each do |puppet_module|
# Create data about this module
md = LoaderModuleData.new(puppet_module)
mr[puppet_module.name] = md
- md.public_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader, self, md.name, md.path, md.name)
+ md.public_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(parent_loader, self, md.name, md.path)
end
# NOTE: Do not resolve all modules here - this is wasteful if only a subset of modules / functions are used
# The resolution is triggered by asking for a module's private loader, since this means there is interest
# in the visibility from that perspective.
# If later, it is wanted that all resolutions should be made up-front (to capture errors eagerly, this
# can be introduced (better for production), but may be irritating in development mode.
end
# =LoaderModuleData
# Information about a Module and its loaders.
# TODO: should have reference to real model element containing all module data; this is faking it
# TODO: Should use Puppet::Module to get the metadata (as a hash) - a somewhat blunt instrument, but that is
# what is available with a reasonable API.
#
class LoaderModuleData
attr_accessor :state
attr_accessor :public_loader
attr_accessor :private_loader
attr_accessor :resolutions
# The Puppet::Module this LoaderModuleData represents in the loader configuration
attr_reader :puppet_module
# @param puppet_module [Puppet::Module] the module instance for the module being represented
#
def initialize(puppet_module)
@state = :initial
@puppet_module = puppet_module
@resolutions = []
@public_loader = nil
@private_loader = nil
end
def name
@puppet_module.name
end
def version
@puppet_module.version
end
def path
@puppet_module.path
end
def resolved?
@state == :resolved
end
def restrict_to_dependencies?
@puppet_module.has_metadata?
end
def unmet_dependencies?
@puppet_module.unmet_dependencies.any?
end
def dependency_names
@puppet_module.dependencies_as_modules.collect(&:name)
end
end
# Resolves module loaders - resolution of model dependencies is done by Puppet::Module
#
class ModuleResolver
def initialize()
@index = {}
@all_module_loaders = nil
end
def [](name)
@index[name]
end
def []=(name, module_data)
@index[name] = module_data
end
def all_module_loaders
@all_module_loaders ||= @index.values.map {|md| md.public_loader }
end
def resolve(module_data)
if module_data.resolved?
return
else
module_data.private_loader =
if module_data.restrict_to_dependencies?
create_loader_with_only_dependencies_visible(module_data)
else
create_loader_with_all_modules_visible(module_data)
end
end
end
private
def create_loader_with_all_modules_visible(from_module_data)
Puppet.debug("ModuleLoader: module '#{from_module_data.name}' has unknown dependencies - it will have all other modules visible")
Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, all_module_loaders())
end
def create_loader_with_only_dependencies_visible(from_module_data)
if from_module_data.unmet_dependencies?
Puppet.warning("ModuleLoader: module '#{from_module_data.name}' has unresolved dependencies"+
" - it will only see those that are resolved."+
" Use 'puppet module list --tree' to see information about modules")
end
dependency_loaders = from_module_data.dependency_names.collect { |name| @index[name].public_loader }
Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, dependency_loaders)
end
end
end
diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb
index 4ca356b16..c947dac6d 100644
--- a/lib/puppet/provider/package/pacman.rb
+++ b/lib/puppet/provider/package/pacman.rb
@@ -1,234 +1,234 @@
require 'puppet/provider/package'
require 'set'
require 'uri'
Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do
desc "Support for the Package Manager Utility (pacman) used in Archlinux.
This provider supports the `install_options` attribute, which allows command-line flags to be passed to pacman.
These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
or an array where each element is either a string or a hash."
commands :pacman => "/usr/bin/pacman"
# Yaourt is a common AUR helper which, if installed, we can use to query the AUR
commands :yaourt => "/usr/bin/yaourt" if Puppet::FileSystem.exist? '/usr/bin/yaourt'
- confine :operatingsystem => :archlinux
- defaultfor :operatingsystem => :archlinux
+ confine :operatingsystem => [:archlinux, :manjarolinux]
+ defaultfor :operatingsystem => [:archlinux, :manjarolinux]
has_feature :install_options
has_feature :uninstall_options
has_feature :upgradeable
# If yaourt is installed, we can make use of it
def yaourt?
return Puppet::FileSystem.exist?('/usr/bin/yaourt')
end
# Install a package using 'pacman', or 'yaourt' if available.
# Installs quietly, without confirmation or progressbar, updates package
# list from servers defined in pacman.conf.
def install
if @resource[:source]
install_from_file
else
install_from_repo
end
unless self.query
raise Puppet::ExecutionFailure.new("Could not find package %s" % self.name)
end
end
def install_from_repo
if yaourt?
cmd = %w{--noconfirm}
cmd += install_options if @resource[:install_options]
cmd << "-S" << @resource[:name]
yaourt *cmd
else
cmd = %w{--noconfirm --noprogressbar}
cmd += install_options if @resource[:install_options]
cmd << "-Sy" << @resource[:name]
pacman *cmd
end
end
private :install_from_repo
def install_from_file
source = @resource[:source]
begin
source_uri = URI.parse source
rescue => detail
self.fail Puppet::Error, "Invalid source '#{source}': #{detail}", detail
end
source = case source_uri.scheme
when nil then source
when /https?/i then source
when /ftp/i then source
when /file/i then source_uri.path
when /puppet/i
fail "puppet:// URL is not supported by pacman"
else
fail "Source #{source} is not supported by pacman"
end
pacman "--noconfirm", "--noprogressbar", "-Sy"
pacman "--noconfirm", "--noprogressbar", "-U", source
end
private :install_from_file
def self.listcmd
[command(:pacman), "-Q"]
end
# Pacman has a concept of package groups as well.
# Package groups have no versions.
def self.listgroupcmd
[command(:pacman), "-Qg"]
end
# Get installed packages (pacman -Q)
def self.installedpkgs
packages = []
begin
execpipe(listcmd()) do |process|
# pacman -Q output is 'packagename version-rel'
regex = %r{^(\S+)\s(\S+)}
fields = [:name, :ensure]
hash = {}
process.each_line { |line|
if match = regex.match(line)
fields.zip(match.captures) { |field,value|
hash[field] = value
}
hash[:provider] = self.name
packages << new(hash)
hash = {}
else
warning("Failed to match line %s" % line)
end
}
end
rescue Puppet::ExecutionFailure
return nil
end
packages
end
# Get installed groups (pacman -Qg)
def self.installedgroups
packages = []
begin
execpipe(listgroupcmd()) do |process|
# pacman -Qg output is 'groupname packagename'
# Groups need to be deduplicated
groups = Set[]
process.each_line { |line|
groups.add(line.split[0])
}
groups.each { |line|
hash = {
:name => line,
:ensure => "1", # Groups don't have versions, so ensure => latest
# will still cause a reinstall.
:provider => self.name
}
packages << new(hash)
}
end
rescue Puppet::ExecutionFailure
return nil
end
packages
end
# Fetch the list of packages currently installed on the system.
def self.instances
packages = self.installedpkgs
groups = self.installedgroups
result = nil
if (!packages && !groups)
nil
elsif (packages && groups)
packages.concat(groups)
else
packages
end
end
# Because Archlinux is a rolling release based distro, installing a package
# should always result in the newest release.
def update
# Install in pacman can be used for update, too
self.install
end
# We rescue the main check from Pacman with a check on the AUR using yaourt, if installed
def latest
pacman "-Sy"
pacman_check = true # Query the main repos first
begin
if pacman_check
output = pacman "-Sp", "--print-format", "%v", @resource[:name]
return output.chomp
else
output = yaourt "-Qma", @resource[:name]
output.split("\n").each do |line|
return line.split[1].chomp if line =~ /^aur/
end
end
rescue Puppet::ExecutionFailure
if pacman_check and self.yaourt?
pacman_check = false # now try the AUR
retry
else
raise
end
end
end
# Querys the pacman master list for information about the package.
def query
begin
output = pacman("-Qi", @resource[:name])
if output =~ /Version.*:\s(.+)/
return { :ensure => $1 }
end
rescue Puppet::ExecutionFailure
return {
:ensure => :purged,
:status => 'missing',
:name => @resource[:name],
:error => 'ok',
}
end
nil
end
# Removes a package from the system.
def uninstall
cmd = %w{--noconfirm --noprogressbar}
cmd += uninstall_options if @resource[:uninstall_options]
cmd << "-R" << @resource[:name]
pacman *cmd
end
private
def install_options
join_options(@resource[:install_options])
end
def uninstall_options
join_options(@resource[:uninstall_options])
end
end
diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb
index 3b4b6388a..93d36d993 100644
--- a/lib/puppet/provider/package/rpm.rb
+++ b/lib/puppet/provider/package/rpm.rb
@@ -1,187 +1,296 @@
require 'puppet/provider/package'
# RPM packaging. Should work anywhere that has rpm installed.
Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Provider::Package do
desc "RPM packaging support; should work anywhere with a working `rpm`
binary.
This provider supports the `install_options` and `uninstall_options`
attributes, which allow command-line flags to be passed to rpm.
These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
or an array where each element is either a string or a hash."
has_feature :versionable
has_feature :install_options
has_feature :uninstall_options
has_feature :virtual_packages
# Note: self:: is required here to keep these constants in the context of what will
# eventually become this Puppet::Type::Package::ProviderRpm class.
# The query format by which we identify installed packages
self::NEVRA_FORMAT = %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\\n}
self::NEVRA_REGEX = %r{^(\S+) (\S+) (\S+) (\S+) (\S+)$}
self::NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch]
commands :rpm => "rpm"
if command('rpm')
confine :true => begin
rpm('--version')
rescue Puppet::ExecutionFailure
false
else
true
end
end
def self.current_version
return @current_version unless @current_version.nil?
output = rpm "--version"
@current_version = output.gsub('RPM version ', '').strip
end
# rpm < 4.1 does not support --nosignature
def self.nosignature
'--nosignature' unless Puppet::Util::Package.versioncmp(current_version, '4.1') < 0
end
# rpm < 4.0.2 does not support --nodigest
def self.nodigest
'--nodigest' unless Puppet::Util::Package.versioncmp(current_version, '4.0.2') < 0
end
def self.instances
packages = []
# list out all of the packages
begin
execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{self::NEVRA_FORMAT}'") { |process|
# now turn each returned line into a package object
process.each_line { |line|
hash = nevra_to_hash(line)
packages << new(hash) unless hash.empty?
}
}
rescue Puppet::ExecutionFailure
raise Puppet::Error, "Failed to list packages", $!.backtrace
end
packages
end
# Find the fully versioned package name and the version alone. Returns
# a hash with entries :instance => fully versioned package name, and
# :ensure => version-release
def query
#NOTE: Prior to a fix for issue 1243, this method potentially returned a cached value
#IF YOU CALL THIS METHOD, IT WILL CALL RPM
#Use get(:property) to check if cached values are available
cmd = ["-q", @resource[:name], "#{self.class.nosignature}", "#{self.class.nodigest}", "--qf", self.class::NEVRA_FORMAT]
begin
output = rpm(*cmd)
rescue Puppet::ExecutionFailure
return nil unless @resource.allow_virtual?
# rpm -q exits 1 if package not found
# retry the query for virtual packages
cmd << '--whatprovides'
begin
output = rpm(*cmd)
rescue Puppet::ExecutionFailure
# couldn't find a virtual package either
return nil
end
end
# FIXME: We could actually be getting back multiple packages
# for multilib and this will only return the first such package
@property_hash.update(self.class.nevra_to_hash(output))
@property_hash.dup
end
# Here we just retrieve the version from the file specified in the source.
def latest
unless source = @resource[:source]
@resource.fail "RPMs must specify a package source"
end
cmd = [command(:rpm), "-q", "--qf", self.class::NEVRA_FORMAT, "-p", source]
h = self.class.nevra_to_hash(execfail(cmd, Puppet::Error))
h[:ensure]
end
def install
unless source = @resource[:source]
@resource.fail "RPMs must specify a package source"
end
# RPM gets pissy if you try to install an already
# installed package
if @resource.should(:ensure) == @property_hash[:ensure] or
@resource.should(:ensure) == :latest && @property_hash[:ensure] == latest
return
end
flag = ["-i"]
flag = ["-U", "--oldpackage"] if @property_hash[:ensure] and @property_hash[:ensure] != :absent
flag += install_options if resource[:install_options]
rpm flag, source
end
def uninstall
query if get(:arch) == :absent
nvr = "#{get(:name)}-#{get(:version)}-#{get(:release)}"
arch = ".#{get(:arch)}"
# If they specified an arch in the manifest, erase that Otherwise,
# erase the arch we got back from the query. If multiple arches are
# installed and only the package name is specified (without the
# arch), this will uninstall all of them on successive runs of the
# client, one after the other
# version of RPM prior to 4.2.1 can't accept the architecture as
# part of the package name.
unless Puppet::Util::Package.versioncmp(self.class.current_version, '4.2.1') < 0
if @resource[:name][-arch.size, arch.size] == arch
nvr += arch
else
nvr += ".#{get(:arch)}"
end
end
flag = ['-e']
flag += uninstall_options if resource[:uninstall_options]
rpm flag, nvr
end
def update
self.install
end
def install_options
join_options(resource[:install_options])
end
def uninstall_options
join_options(resource[:uninstall_options])
end
+ # This is an attempt at implementing RPM's
+ # lib/rpmvercmp.c rpmvercmp(a, b) in Ruby.
+ #
+ # Some of the things in here look REALLY
+ # UGLY and/or arbitrary. Our goal is to
+ # match how RPM compares versions, quirks
+ # and all.
+ #
+ # I've kept a lot of C-like string processing
+ # in an effort to keep this as identical to RPM
+ # as possible.
+ #
+ # returns 1 if str1 is newer than str2,
+ # 0 if they are identical
+ # -1 if str1 is older than str2
+ def rpmvercmp(str1, str2)
+ return 0 if str1 == str2
+
+ front_strip_re = /^[^A-Za-z0-9~]+/
+ segment_re = /^[A-Za-z0-9]/
+ # these represent RPM rpmio/rpmstring.c functions
+ risalnum = /[A-Za-z0-9]/
+ risdigit = /^[0-9]+/
+ risalpha = /[A-Za-z]/
+
+ while str1.length > 0 or str2.length > 0
+ # trim anything that's !risalnum() and != '~' off the beginning of each string
+ str1 = str1.gsub(front_strip_re, '')
+ str2 = str2.gsub(front_strip_re, '')
+
+ # "handle the tilde separator, it sorts before everything else"
+ if /^~/.match(str1) && /^~/.match(str2)
+ # if they both have ~, strip it
+ str1 = str1[1..-1]
+ str2 = str2[1..-1]
+ elsif /^~/.match(str1)
+ return -1
+ elsif /^~/.match(str2)
+ return 1
+ end
+
+ break if str1.length == 0 or str2.length == 0
+
+ # "grab first completely alpha or completely numeric segment"
+ isnum = false
+ # if the first char of str1 is a digit, grab the chunk of continuous digits from each string
+ if risdigit.match(str1)
+ if str1 =~ /^[0-9]+/
+ segment1 = $~.to_s
+ str1 = $~.post_match
+ else
+ segment1 = ''
+ end
+ if str2 =~ /^[0-9]+/
+ segment2 = $~.to_s
+ str2 = $~.post_match
+ else
+ segment2 = ''
+ end
+ isnum = true
+ # else grab the chunk of continuous alphas from each string (which may be '')
+ else
+ if str1 =~ /^[A-Za-z]+/
+ segment1 = $~.to_s
+ str1 = $~.post_match
+ else
+ segment1 = ''
+ end
+ if str2 =~ /^[A-Za-z]+/
+ segment2 = $~.to_s
+ str2 = $~.post_match
+ else
+ segment2 = ''
+ end
+ end
+
+ # if the segments we just grabbed from the strings are different types (i.e. one numeric one alpha),
+ # where alpha also includes ''; "numeric segments are always newer than alpha segments"
+ if segment2.length == 0
+ return 1 if isnum
+ return -1
+ end
+
+ if isnum
+ # "throw away any leading zeros - it's a number, right?"
+ segment1 = segment1.gsub(/^0+/, '')
+ segment2 = segment2.gsub(/^0+/, '')
+ # "whichever number has more digits wins"
+ return 1 if segment1.length > segment2.length
+ return -1 if segment1.length < segment2.length
+ end
+
+ # "strcmp will return which one is greater - even if the two segments are alpha
+ # or if they are numeric. don't return if they are equal because there might
+ # be more segments to compare"
+ rc = segment1 <=> segment2
+ return rc if rc != 0
+ end #end while loop
+
+ # if we haven't returned anything yet, "whichever version still has characters left over wins"
+ if str1.length > str2.length
+ return 1
+ elsif str1.length < str2.length
+ return -1
+ else
+ return 0
+ end
+ end
+
private
# @param line [String] one line of rpm package query information
# @return [Hash] of NEVRA_FIELDS strings parsed from package info
# or an empty hash if we failed to parse
# @api private
def self.nevra_to_hash(line)
line.strip!
hash = {}
if match = self::NEVRA_REGEX.match(line)
self::NEVRA_FIELDS.zip(match.captures) { |f, v| hash[f] = v }
hash[:provider] = self.name
hash[:ensure] = "#{hash[:version]}-#{hash[:release]}"
else
Puppet.debug("Failed to match rpm line #{line}")
end
return hash
end
end
diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb
index bfbe9df16..e93379f8b 100644
--- a/lib/puppet/provider/package/yum.rb
+++ b/lib/puppet/provider/package/yum.rb
@@ -1,199 +1,264 @@
-require 'puppet/util/package'
-
Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
desc "Support via `yum`.
Using this provider's `uninstallable` feature will not remove dependent packages. To
remove dependent packages with this provider use the `purgeable` feature, but note this
feature is destructive and should be used with the utmost care.
This provider supports the `install_options` attribute, which allows command-line flags to be passed to yum.
These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
or an array where each element is either a string or a hash."
has_feature :install_options, :versionable, :virtual_packages
commands :yum => "yum", :rpm => "rpm", :python => "python"
self::YUMHELPER = File::join(File::dirname(__FILE__), "yumhelper.py")
if command('rpm')
confine :true => begin
rpm('--version')
rescue Puppet::ExecutionFailure
false
else
true
end
end
defaultfor :osfamily => :redhat
def self.prefetch(packages)
raise Puppet::Error, "The yum provider can only be used as root" if Process.euid != 0
super
end
# Retrieve the latest package version information for a given package name
# and combination of repos to enable and disable.
#
# @note If multiple package versions are defined (such as in the case where a
# package is built for multiple architectures), the first package found
# will be used.
#
# @api private
# @param package [String] The name of the package to query
# @param enablerepo [Array] A list of repositories to enable for this query
# @param disablerepo [Array] A list of repositories to disable for this query
# @return [Hash]
def self.latest_package_version(package, enablerepo, disablerepo)
key = [enablerepo, disablerepo]
@latest_versions ||= {}
if @latest_versions[key].nil?
@latest_versions[key] = fetch_latest_versions(enablerepo, disablerepo)
end
if @latest_versions[key][package]
@latest_versions[key][package].first
end
end
# Search for all installed packages that have newer versions, given a
# combination of repositories to enable and disable.
#
# @api private
# @param enablerepo [Array] A list of repositories to enable for this query
# @param disablerepo [Array] A list of repositories to disable for this query
# @return [Hash>>] All packages that were
# found with a list of found versions for each package.
def self.fetch_latest_versions(enablerepo, disablerepo)
latest_versions = Hash.new {|h, k| h[k] = []}
args = [self::YUMHELPER]
args.concat(enablerepo.map { |repo| ['-e', repo] }.flatten)
args.concat(disablerepo.map { |repo| ['-d', repo] }.flatten)
python(args).scan(/^_pkg (.*)$/) do |match|
hash = nevra_to_hash(match[0])
# Create entries for both the package name without a version and a
# version since yum considers those as mostly interchangeable.
short_name = hash[:name]
long_name = "#{hash[:name]}.#{hash[:arch]}"
latest_versions[short_name] << hash
latest_versions[long_name] << hash
end
latest_versions
end
def self.clear
@latest_versions = nil
end
def install
wanted = @resource[:name]
# If not allowing virtual packages, do a query to ensure a real package exists
unless @resource.allow_virtual?
yum *['-d', '0', '-e', '0', '-y', install_options, :list, wanted].compact
end
should = @resource.should(:ensure)
self.debug "Ensuring => #{should}"
operation = :install
case should
when true, false, Symbol
# pass
should = nil
else
# Add the package version
wanted += "-#{should}"
is = self.query
- if is && Puppet::Util::Package.versioncmp(should, is[:ensure]) < 0
+ if is && yum_compareEVR(yum_parse_evr(should), yum_parse_evr(is[:ensure])) < 0
self.debug "Downgrading package #{@resource[:name]} from version #{is[:ensure]} to #{should}"
operation = :downgrade
end
end
args = ["-d", "0", "-e", "0", "-y", install_options, operation, wanted].compact
yum *args
# If a version was specified, query again to see if it is a matching version
if should
is = self.query
raise Puppet::Error, "Could not find package #{self.name}" unless is
# FIXME: Should we raise an exception even if should == :latest
# and yum updated us to a version other than @param_hash[:ensure] ?
- raise Puppet::Error, "Failed to update to version #{should}, got version #{is[:ensure]} instead" if should != is[:ensure]
+ vercmp_result = yum_compareEVR(yum_parse_evr(should), yum_parse_evr(is[:ensure]))
+ raise Puppet::Error, "Failed to update to version #{should}, got version #{is[:ensure]} instead" if vercmp_result != 0
end
end
# What's the latest package version available?
def latest
upd = self.class.latest_package_version(@resource[:name], enablerepo, disablerepo)
unless upd.nil?
# FIXME: there could be more than one update for a package
# because of multiarch
return "#{upd[:epoch]}:#{upd[:version]}-#{upd[:release]}"
else
# Yum didn't find updates, pretend the current
# version is the latest
raise Puppet::DevError, "Tried to get latest on a missing package" if properties[:ensure] == :absent
return properties[:ensure]
end
end
def update
# Install in yum can be used for update, too
self.install
end
def purge
yum "-y", :erase, @resource[:name]
end
+ # parse a yum "version" specification
+ # this re-implements yum's
+ # rpmUtils.miscutils.stringToVersion() in ruby
+ def yum_parse_evr(s)
+ ei = s.index(':')
+ if ei
+ e = s[0,ei]
+ s = s[ei+1,s.length]
+ else
+ e = nil
+ end
+ e = String(Bignum(e)) rescue '0'
+ ri = s.index('-')
+ if ri
+ v = s[0,ri]
+ r = s[ri+1,s.length]
+ else
+ v = s
+ r = nil
+ end
+ return { :epoch => e, :version => v, :release => r }
+ end
+
+ # how yum compares two package versions:
+ # rpmUtils.miscutils.compareEVR(), which massages data types and then calls
+ # rpm.labelCompare(), found in rpm.git/python/header-py.c, which
+ # sets epoch to 0 if null, then compares epoch, then ver, then rel
+ # using compare_values() and returns the first non-0 result, else 0.
+ # This function combines the logic of compareEVR() and labelCompare().
+ #
+ # "version_should" can be v, v-r, or e:v-r.
+ # "version_is" will always be at least v-r, can be e:v-r
+ def yum_compareEVR(should_hash, is_hash)
+ # pass on to rpm labelCompare
+ rc = compare_values(should_hash[:epoch], is_hash[:epoch])
+ return rc unless rc == 0
+ rc = compare_values(should_hash[:version], is_hash[:version])
+ return rc unless rc == 0
+
+ # here is our special case, PUP-1244.
+ # if should_hash[:release] is nil (not specified by the user),
+ # and comparisons up to here are equal, return equal. We need to
+ # evaluate to whatever level of detail the user specified, so we
+ # don't end up upgrading or *downgrading* when not intended.
+ #
+ # This should NOT be triggered if we're trying to ensure latest.
+ return 0 if should_hash[:release].nil?
+
+ rc = compare_values(should_hash[:release], is_hash[:release])
+ return rc
+ end
+
+ # this method is a native implementation of the
+ # compare_values function in rpm's python bindings,
+ # found in python/header-py.c, as used by yum.
+ def compare_values(s1, s2)
+ if s1.nil? && s2.nil?
+ return 0
+ elsif ( not s1.nil? ) && s2.nil?
+ return 1
+ elsif s1.nil? && (not s2.nil?)
+ return -1
+ end
+ return rpmvercmp(s1, s2)
+ end
+
# @deprecated
def latest_info
Puppet.deprecation_warning("#{self.class}##{__method__} is deprecated and is no longer used.")
@latest_info
end
# @deprecated
def latest_info=(latest)
Puppet.deprecation_warning("#{self.class}##{__method__} is deprecated and is no longer used.")
@latest_info = latest
end
private
def enablerepo
scan_options(resource[:install_options], '--enablerepo')
end
def disablerepo
scan_options(resource[:install_options], '--disablerepo')
end
# Scan a structure that looks like the package type 'install_options'
# structure for all hashes that have a specific key.
#
# @api private
# @param options [Array, nil] The options structure. If the
# options are nil an empty array will be returned.
# @param key [String] The key to look for in all contained hashes
# @return [Array] All hash values with the given key.
def scan_options(options, key)
return [] if options.nil?
options.inject([]) do |repos, opt|
if opt.is_a? Hash and opt[key]
repos << opt[key]
end
repos
end
end
end
diff --git a/lib/puppet/provider/service/openbsd.rb b/lib/puppet/provider/service/openbsd.rb
index 521462b78..c84fc3d87 100644
--- a/lib/puppet/provider/service/openbsd.rb
+++ b/lib/puppet/provider/service/openbsd.rb
@@ -1,342 +1,93 @@
Puppet::Type.type(:service).provide :openbsd, :parent => :init do
desc "Provider for OpenBSD's rc.d daemon control scripts"
+ commands :rcctl => '/usr/sbin/rcctl'
+
confine :operatingsystem => :openbsd
defaultfor :operatingsystem => :openbsd
has_feature :flaggable
- def self.rcconf() '/etc/rc.conf' end
- def self.rcconf_local() '/etc/rc.conf.local' end
-
- def self.defpath
- ["/etc/rc.d"]
+ def startcmd
+ [command(:rcctl), '-f', :start, @resource[:name]]
end
- def startcmd
- [self.initscript, "-f", :start]
+ def stopcmd
+ [command(:rcctl), :stop, @resource[:name]]
end
def restartcmd
- (@resource[:hasrestart] == :true) && [self.initscript, "-f", :restart]
+ (@resource[:hasrestart] == :true) && [command(:rcctl), '-f', :restart, @resource[:name]]
end
def statuscmd
- [self.initscript, :check]
- end
-
- # Fetch the state of all service resources
- def self.prefetch(resources)
- services = instances
- resources.keys.each do |name|
- if provider = services.find { |svc| svc.name == name }
- resources[name].provider = provider
- end
- end
- end
-
- # Return the list of rc scripts
- # @api private
- def self.rclist
- unless @rclist
- Puppet.debug "Building list of rc scripts"
- @rclist = []
- defpath.each do |p|
- Dir.glob(p + '/*').each do |item|
- @rclist << item if File.executable?(item)
- end
- end
- end
- @rclist
- end
-
- # Return a hash where the keys are the rc script names as symbols with flags
- # as values
- # @api private
- def self.rcflags
- unless @flag_hash
- Puppet.debug "Retrieving flags for all discovered services"
-
- Puppet.debug "Reading the contents of the rc conf files"
-
- if File.exists?(rcconf_local())
- rcconf_local_contents = File.readlines(rcconf_local())
- else
- rcconf_local_contents = []
- end
-
- if File.exists?(rcconf())
- rcconf_contents = File.readlines(rcconf())
- else
- rcconf_contents = []
- end
-
- @flag_hash = {}
- rclist().each do |rcitem|
-
- rcname = rcitem.split('/').last
-
- if flagline = rcconf_local_contents.find {|l| l =~ /^#{rcname}_flags/ }
- flag = parse_rc_line(flagline)
- @flag_hash[rcname.to_sym] ||= flag
- end
-
- # For the defaults, if the flags are set to 'NO', we skip setting the
- # flag here, since it will already be disabled, and this makes the
- # output of `puppet resource service` a bit more correct.
- if flagline = rcconf_contents.find {|l| l =~ /^#{rcname}_flags/ }
- flag = parse_rc_line(flagline)
- unless flag == "NO"
- @flag_hash[rcname.to_sym] ||= flag
- end
- end
- @flag_hash[rcname.to_sym] ||= nil
- end
- end
- @flag_hash
+ [command(:rcctl), :check, @resource[:name]]
end
# @api private
- def self.parse_rc_line(rc_line)
- rc_line.sub!(/\s*#(.*)$/,'')
- regex = /\w+_flags=(.*)/
- rc_line.match(regex)[1].gsub(/^"/,'').gsub(/"$/,'')
- end
-
- # Read the rc.conf* files and determine the value of the flags
- # @api private
- def self.get_flags(rcname)
- rcflags()
- @flag_hash[rcname.to_sym]
- end
-
+ # When storing the name, take into account not everything has
+ # '_flags', like 'multicast_host' and 'pf'.
def self.instances
instances = []
- defpath.each do |path|
- unless File.directory?(path)
- Puppet.debug "Service path #{path} does not exist"
- next
- end
- rclist().each do |d|
- instances << new(
- :name => File.basename(d),
- :path => path,
- :flags => get_flags(File.basename(d)),
- :hasstatus => true
- )
- end
- end
- instances
- end
-
- # @api private
- def rcvar_name
- self.name + '_flags'
- end
-
- # @api private
- def read_rcconf_local_text()
- if File.exists?(self.class.rcconf_local())
- File.read(self.class.rcconf_local())
- else
- []
- end
- end
-
- # @api private
- def load_rcconf_local_array
- if File.exists?(self.class.rcconf_local())
- File.readlines(self.class.rcconf_local()).map {|l|
- l.chomp!
- }
- else
- []
- end
- end
-
- # @api private
- def write_rc_contents(file, text)
- Puppet::Util.replace_file(file, 0644) do |f|
- f.write(text)
- end
- end
-
- # @api private
- def set_content_flags(content,flags)
- unless content.is_a? Array
- debug "content must be an array at flags"
- return ""
- else
- content.reject! {|l| l.nil? }
- end
-
- if flags.nil? or flags.size == 0
- if in_base?
- append = resource[:name] + '_flags=""'
+ begin
+ execpipe([command(:rcctl), :status]) do |process|
+ process.each_line do |line|
+ match = /^(.*?)(?:_flags)?=(.*)$/.match(line)
+ attributes_hash = {
+ :name => match[1],
+ :flags => match[2],
+ :hasstatus => true,
+ :provider => :openbsd,
+ }
+
+ instances << new(attributes_hash);
+ end
end
- else
- append = resource[:name] + '_flags="' + flags + '"'
+ instances
+ rescue Puppet::ExecutionFailure
+ return nil
end
-
- if content.find {|l| l =~ /#{resource[:name]}_flags/ }.nil?
- content << append
- else
- content.map {|l| l.gsub!(/^#{resource[:name]}_flags="(.*)?"(.*)?$/, append) }
- end
- content
end
- # @api private
- def remove_content_flags(content)
- content.reject {|l| l =~ /#{resource[:name]}_flags/ }
- end
-
- # return an array of the currently enabled pkg_scripts
- # @api private
- def pkg_scripts
- current = load_rcconf_local_array()
- if scripts = current.find{|l| l =~ /^pkg_scripts/ }
- if match = scripts.match(/^pkg_scripts="(.*)?"(.*)?$/)
- match[1].split(' ')
- else
- []
- end
- else
- []
- end
- end
-
- # return the array with the current resource added
- # @api private
- def pkg_scripts_append
- [pkg_scripts(), resource[:name]].flatten.uniq
- end
-
- # return the array without the current resource
- # @api private
- def pkg_scripts_remove
- pkg_scripts().reject {|s| s == resource[:name] }
- end
-
- # Modify the content array to contain the requsted pkg_scripts line and retun
- # the resulting array
- # @api private
- def set_content_scripts(content,scripts)
- unless content.is_a? Array
- debug "content must be an array at scripts"
- return ""
- else
- content.reject! {|l| l.nil? }
- end
-
- scripts_line = 'pkg_scripts="' + scripts.join(' ') + '"'
+ def enabled?
+ output = execute([command(:rcctl), "status", @resource[:name]],
+ :failonfail => false, :combine => false, :squelch => false).chomp
- if content.find {|l| l =~ /^pkg_scripts/ }.nil?
- content << scripts_line
+ if output == 'NO'
+ self.debug("Is disabled")
+ return :false
else
- # Replace the found pkg_scripts line with our own
- content.each_with_index {|l,i|
- if l =~ /^pkg_scripts/
- content[i] = scripts_line
- end
- }
+ self.debug("Is enabled")
+ return :true
end
- content
- end
-
- # Determine if the rc script is included in base
- # @api private
- def in_base?
- script = File.readlines(self.class.rcconf).find {|s| s =~ /^#{rcvar_name}/ }
- !script.nil?
- end
-
- # @api private
- def default_disabled?
- line = File.readlines(self.class.rcconf).find {|l| l =~ /#{rcvar_name}/ }
- self.class.parse_rc_line(line) == 'NO'
end
- def enabled?
- if in_base?
- if (@property_hash[:flags].nil? or @property_hash[:flags] == 'NO')
- :false
- else
- :true
- end
+ def enable
+ self.debug("Enabling")
+ if @resource[:flags]
+ rcctl(:enable, @resource[:name], :flags, @resource[:flags])
else
- if (pkg_scripts().include?(@property_hash[:name]))
- :true
- else
- :false
- end
+ rcctl(:enable, @resource[:name])
end
end
- def enable
- self.debug("Enabling #{self.name}")
- end
-
- # We should also check for default state
def disable
- self.debug("Disabling #{self.name}")
+ self.debug("Disabling")
+ rcctl(:disable, @resource[:name])
end
+ # Uses the wrapper to prevent failure when the service is not running;
+ # rcctl(8) return non-zero in that case.
def flags
- @property_hash[:flags]
+ output = execute([command(:rcctl), "status", @resource[:name]],
+ :failonfail => false, :combine => false, :squelch => false).chomp
+ self.debug("Flags are: #{output}")
+ output
end
def flags=(value)
- @property_hash[:flags] = value
- end
-
- def flush
- debug "Flusing resource for #{self.name}"
-
- # Here we load the contents of the rc.conf.local file into the contents
- # variable, modify it if needed, and then compare that to the original. If
- # they are different, we write it out.
-
- original = load_rcconf_local_array()
- content = original
-
- debug @property_hash.inspect
-
- if resource[:enable] == :true
- #set_flags(resource[:flags])
- content = set_content_flags(content, resource[:flags])
-
- # We need only add append the resource name to the pkg_scripts if the
- # package is not found in the base system.
-
- if not in_base?
- content = set_content_scripts(content,pkg_scripts_append())
- end
- elsif resource[:enable] == :false
-
- # By virtue of being excluded from the base system, all packages are
- # disabled by default and need not be set in the rc.conf.local at all.
-
- if not in_base?
- content = remove_content_flags(content)
- content = set_content_scripts(content,pkg_scripts_remove())
- else
- if default_disabled?
- content = remove_content_flags(content)
- else
- content = set_content_flags(content, "NO")
- end
- end
- end
-
- # Make sure to append a newline to the end of the file
- unless content[-1] == ""
- content << ""
- end
- output = content.join("\n")
-
- # Write the contents only if necessary, and only once
- write_rc_contents(self.class.rcconf_local(), output)
+ self.debug("Changing flags from #{flags} to #{value}")
+ rcctl(:enable, @resource[:name], :flags, value)
end
end
diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb
index c084ffbc9..2eb55f38b 100644
--- a/lib/puppet/provider/service/windows.rb
+++ b/lib/puppet/provider/service/windows.rb
@@ -1,106 +1,106 @@
# Windows Service Control Manager (SCM) provider
Puppet::Type.type(:service).provide :windows, :parent => :service do
desc <<-EOT
Support for Windows Service Control Manager (SCM). This provider can
start, stop, enable, and disable services, and the SCM provides working
status methods for all services.
Control of service groups (dependencies) is not yet supported, nor is running
services as a specific user.
EOT
defaultfor :operatingsystem => :windows
confine :operatingsystem => :windows
has_feature :refreshable
commands :net => 'net.exe'
def enable
w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START )
raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
- rescue Win32::Service::Error => detail
+ rescue => detail
raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}", detail )
end
def disable
w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED )
raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? )
- rescue Win32::Service::Error => detail
+ rescue => detail
raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}", detail )
end
def manual_start
w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START )
raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
- rescue Win32::Service::Error => detail
+ rescue => detail
raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}", detail )
end
def enabled?
w32ss = Win32::Service.config_info( @resource[:name] )
raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) )
debug("Service #{@resource[:name]} start type is #{w32ss.start_type}")
case w32ss.start_type
when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START),
Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START),
Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START)
:true
when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
:manual
when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
:false
else
raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}")
end
- rescue Win32::Service::Error => detail
+ rescue => detail
raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}", detail )
end
def start
if enabled? == :false
# If disabled and not managing enable, respect disabled and fail.
if @resource[:enable].nil?
raise Puppet::Error, "Will not start disabled service #{@resource[:name]} without managing enable. Specify 'enable => false' to override."
# Otherwise start. If enable => false, we will later sync enable and
# disable the service again.
elsif @resource[:enable] == :true
enable
else
manual_start
end
end
net(:start, @resource[:name])
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}", detail )
end
def stop
net(:stop, @resource[:name])
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error.new("Cannot stop #{@resource[:name]}, error was: #{detail}", detail )
end
def status
w32ss = Win32::Service.status( @resource[:name] )
raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) )
state = case w32ss.current_state
when "stopped", "pause pending", "stop pending", "paused" then :stopped
when "running", "continue pending", "start pending" then :running
else
raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'")
end
debug("Service #{@resource[:name]} is #{w32ss.current_state}")
return state
- rescue Win32::Service::Error => detail
+ rescue => detail
raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}", detail )
end
# returns all providers for all existing services and startup state
def self.instances
Win32::Service.services.collect { |s| new(:name => s.service_name) }
end
end
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index a5419512b..8abb31b0e 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,612 +1,632 @@
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/util/pson'
require 'puppet/parameter'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
#
# @api public
class Puppet::Resource
# This stub class is only needed for serialization compatibility with 0.25.x.
# Specifically, it exists to provide a compatibility API when using YAML
# serialized objects loaded from StoreConfigs.
Reference = Puppet::Resource
include Puppet::Util::Tagging
extend Puppet::Util::Pson
include Enumerable
attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict
attr_reader :type, :title
require 'puppet/indirector'
extend Puppet::Indirector
indirects :resource, :terminus_class => :ral
ATTRIBUTES = [:file, :line, :exported]
def self.from_data_hash(data)
raise ArgumentError, "No resource type provided in serialized data" unless type = data['type']
raise ArgumentError, "No resource title provided in serialized data" unless title = data['title']
resource = new(type, title)
if params = data['parameters']
params.each { |param, value| resource[param] = value }
end
if tags = data['tags']
tags.each { |tag| resource.tag(tag) }
end
ATTRIBUTES.each do |a|
if value = data[a.to_s]
resource.send(a.to_s + "=", value)
end
end
resource
end
def self.from_pson(pson)
Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.")
self.from_data_hash(pson)
end
def inspect
"#{@type}[#{@title}]#{to_hash.inspect}"
end
def to_data_hash
data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param|
next hash unless value = self.send(param)
hash[param.to_s] = value
hash
end
data["exported"] ||= false
params = self.to_hash.inject({}) do |hash, ary|
param, value = ary
# Don't duplicate the title as the namevar
next hash if param == namevar and value == title
hash[param] = Puppet::Resource.value_to_pson_data(value)
hash
end
data["parameters"] = params unless params.empty?
data
end
# This doesn't include document type as it is part of a catalog
def to_pson_data_hash
to_data_hash
end
def self.value_to_pson_data(value)
if value.is_a? Array
value.map{|v| value_to_pson_data(v) }
elsif value.is_a? Puppet::Resource
value.to_s
else
value
end
end
def yaml_property_munge(x)
case x
when Hash
x.inject({}) { |h,kv|
k,v = kv
h[k] = self.class.value_to_pson_data(v)
h
}
else self.class.value_to_pson_data(x)
end
end
YAML_ATTRIBUTES = [:@file, :@line, :@exported, :@type, :@title, :@tags, :@parameters]
# Explicitly list the instance variables that should be serialized when
# converting to YAML.
#
# @api private
# @return [Array] The intersection of our explicit variable list and
# all of the instance variables defined on this class.
def to_yaml_properties
YAML_ATTRIBUTES & super
end
def to_pson(*args)
to_data_hash.to_pson(*args)
end
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
parameters.send(method, *args)
end
end
# Set a given parameter. Converts all passed names
# to lower-case symbols.
def []=(param, value)
validate_parameter(param) if validate_parameters
parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
parameters[parameter_name(param)]
end
def ==(other)
return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title
return false unless to_hash == other.to_hash
true
end
# Compatibility method.
def builtin?
builtin_type?
end
# Is this a builtin resource type?
def builtin_type?
resource_type.is_a?(Class)
end
# Iterate over each param/value pair, as required for Enumerable.
def each
parameters.each { |p,v| yield p, v }
end
def include?(parameter)
super || parameters.keys.include?( parameter_name(parameter) )
end
%w{exported virtual strict}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
def class?
@is_class ||= @type == "Class"
end
def stage?
@is_stage ||= @type.to_s.downcase == "stage"
end
# Cache to reduce respond_to? lookups
@@nondeprecating_type = {}
# Construct a resource from data.
#
# Constructs a resource instance with the given `type` and `title`. Multiple
# type signatures are possible for these arguments and most will result in an
# expensive call to {Puppet::Node::Environment#known_resource_types} in order
# to resolve `String` and `Symbol` Types to actual Ruby classes.
#
# @param type [Symbol, String] The name of the Puppet Type, as a string or
# symbol. The actual Type will be looked up using
# {Puppet::Node::Environment#known_resource_types}. This lookup is expensive.
# @param type [String] The full resource name in the form of
# `"Type[Title]"`. This method of calling should only be used when
# `title` is `nil`.
# @param type [nil] If a `nil` is passed, the title argument must be a string
# of the form `"Type[Title]"`.
# @param type [Class] A class that inherits from `Puppet::Type`. This method
# of construction is much more efficient as it skips calls to
# {Puppet::Node::Environment#known_resource_types}.
#
# @param title [String, :main, nil] The title of the resource. If type is `nil`, may also
# be the full resource name in the form of `"Type[Title]"`.
#
# @api public
def initialize(type, title = nil, attributes = {})
@parameters = {}
if type.is_a?(Class) && type < Puppet::Type
# Set the resource type to avoid an expensive `known_resource_types`
# lookup.
self.resource_type = type
# From this point on, the constructor behaves the same as if `type` had
# been passed as a symbol.
type = type.name
end
# Set things like strictness first.
attributes.each do |attr, value|
next if attr == :parameters
send(attr.to_s + "=", value)
end
@type, @title = extract_type_and_title(type, title)
@type = munge_type_name(@type)
if self.class?
@title = :main if @title == ""
@title = munge_type_name(@title)
end
if params = attributes[:parameters]
extract_parameters(params)
end
if resource_type and ! @@nondeprecating_type[resource_type]
if resource_type.respond_to?(:deprecate_params)
resource_type.deprecate_params(title, attributes[:parameters])
else
@@nondeprecating_type[resource_type] = true
end
end
tag(self.type)
tag(self.title) if valid_tag?(self.title)
@reference = self # for serialization compatibility with 0.25.x
if strict? and ! resource_type
if self.class?
raise ArgumentError, "Could not find declared class #{title}"
else
raise ArgumentError, "Invalid resource type #{type}"
end
end
end
def ref
to_s
end
# Find our resource.
def resolve
catalog ? catalog.resource(to_s) : nil
end
# The resource's type implementation
# @return [Puppet::Type, Puppet::Resource::Type]
# @api private
def resource_type
@rstype ||= case type
when "Class"; environment.known_resource_types.hostclass(title == :main ? "" : title)
when "Node"; environment.known_resource_types.node(title)
else
Puppet::Type.type(type) || environment.known_resource_types.definition(type)
end
end
# Set the resource's type implementation
# @param type [Puppet::Type, Puppet::Resource::Type]
# @api private
def resource_type=(type)
@rstype = type
end
def environment
@environment ||= if catalog
catalog.environment_instance
else
Puppet.lookup(:current_environment) { Puppet::Node::Environment::NONE }
end
end
def environment=(environment)
@environment = environment
end
# Produce a simple hash of our parameters.
def to_hash
parse_title.merge parameters
end
def to_s
"#{type}[#{title}]"
end
def uniqueness_key
# Temporary kludge to deal with inconsistant use patters
h = self.to_hash
h[namevar] ||= h[:name]
h[:name] ||= h[namevar]
h.values_at(*key_attributes.sort_by { |k| k.to_s })
end
def key_attributes
resource_type.respond_to?(:key_attributes) ? resource_type.key_attributes : [:name]
end
+ # Convert our resource to yaml for Hiera purposes.
+ def to_hierayaml
+ # Collect list of attributes to align => and move ensure first
+ attr = parameters.keys
+ attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max }
+
+ attr.sort!
+ if attr.first != :ensure && attr.include?(:ensure)
+ attr.delete(:ensure)
+ attr.unshift(:ensure)
+ end
+
+ attributes = attr.collect { |k|
+ v = parameters[k]
+ " %-#{attr_max}s: %s\n" % [k, Puppet::Parameter.format_value_for_display(v)]
+ }.join
+
+ " %s:\n%s" % [self.title, attributes]
+ end
+
# Convert our resource to Puppet code.
def to_manifest
# Collect list of attributes to align => and move ensure first
attr = parameters.keys
attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max }
attr.sort!
if attr.first != :ensure && attr.include?(:ensure)
attr.delete(:ensure)
attr.unshift(:ensure)
end
attributes = attr.collect { |k|
v = parameters[k]
" %-#{attr_max}s => %s,\n" % [k, Puppet::Parameter.format_value_for_display(v)]
}.join
"%s { '%s':\n%s}" % [self.type.to_s.downcase, self.title, attributes]
end
def to_ref
ref
end
# Convert our resource to a RAL resource instance. Creates component
# instances for resource types that don't exist.
def to_ral
typeklass = Puppet::Type.type(self.type) || Puppet::Type.type(:component)
typeklass.new(self)
end
def name
# this is potential namespace conflict
# between the notion of an "indirector name"
# and a "resource name"
[ type, title ].join('/')
end
def missing_arguments
resource_type.arguments.select do |param, default|
param = param.to_sym
parameters[param].nil? || parameters[param].value == :undef
end
end
private :missing_arguments
# Consult external data bindings for class parameter values which must be
# namespaced in the backend.
#
# Example:
#
# class foo($port=0){ ... }
#
# We make a request to the backend for the key 'foo::port' not 'foo'
#
def lookup_external_default_for(param, scope)
# Only lookup parameters for host classes
return nil unless resource_type.type == :hostclass
name = "#{resource_type.name}::#{param}"
lookup_with_databinding(name, scope)
end
private :lookup_external_default_for
def lookup_with_databinding(name, scope)
begin
Puppet::DataBinding.indirection.find(
name,
:environment => scope.environment.to_s,
:variables => scope)
rescue Puppet::DataBinding::LookupError => e
raise Puppet::Error.new("Error from DataBinding '#{Puppet[:data_binding_terminus]}' while looking up '#{name}': #{e.message}", e)
end
end
private :lookup_with_databinding
def set_default_parameters(scope)
return [] unless resource_type and resource_type.respond_to?(:arguments)
unless is_a?(Puppet::Parser::Resource)
fail Puppet::DevError, "Cannot evaluate default parameters for #{self} - not a parser resource"
end
missing_arguments.collect do |param, default|
external_value = lookup_external_default_for(param, scope)
if external_value.nil? && default.nil?
next
elsif external_value.nil?
value = default.safeevaluate(scope)
else
value = external_value
end
self[param.to_sym] = value
param
end.compact
end
def copy_as_resource
result = Puppet::Resource.new(type, title)
result.file = self.file
result.line = self.line
result.exported = self.exported
result.virtual = self.virtual
result.tag(*self.tags)
result.environment = environment
result.instance_variable_set(:@rstype, resource_type)
to_hash.each do |p, v|
if v.is_a?(Puppet::Resource)
v = Puppet::Resource.new(v.type, v.title)
elsif v.is_a?(Array)
# flatten resource references arrays
v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) }
v = v.collect do |av|
av = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource)
av
end
end
if Puppet[:parser] == 'current'
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
#
# This behavior is not done in the future parser, but we can't issue a
# deprecation warning either since there isn't anything that a user can
# do about it.
result[p] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
else
result[p] = v
end
end
result
end
def valid_parameter?(name)
resource_type.valid_parameter?(name)
end
# Verify that all required arguments are either present or
# have been provided with defaults.
# Must be called after 'set_default_parameters'. We can't join the methods
# because Type#set_parameters needs specifically ordered behavior.
def validate_complete
return unless resource_type and resource_type.respond_to?(:arguments)
resource_type.arguments.each do |param, default|
param = param.to_sym
fail Puppet::ParseError, "Must pass #{param} to #{self}" unless parameters.include?(param)
end
# Perform optional type checking
if Puppet[:parser] == 'future'
# Perform type checking
arg_types = resource_type.argument_types
# Parameters is a map from name, to parameter, and the parameter again has name and value
parameters.each do |name, value|
next unless t = arg_types[name.to_s] # untyped, and parameters are symbols here (aargh, strings in the type)
unless Puppet::Pops::Types::TypeCalculator.instance?(t, value.value)
inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value.value)
actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type)
fail Puppet::ParseError, "Expected parameter '#{name}' of '#{self}' to have type #{t.to_s}, got #{actual.to_s}"
end
end
end
end
def validate_parameter(name)
raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
end
def prune_parameters(options = {})
properties = resource_type.properties.map(&:name)
dup.collect do |attribute, value|
if value.to_s.empty? or Array(value).empty?
delete(attribute)
elsif value.to_s == "absent" and attribute.to_s != "ensure"
delete(attribute)
end
parameters_to_include = options[:parameters_to_include] || []
delete(attribute) unless properties.include?(attribute) || parameters_to_include.include?(attribute)
end
self
end
private
# Produce a canonical method name.
def parameter_name(param)
param = param.to_s.downcase.to_sym
if param == :name and namevar
param = namevar
end
param
end
# The namevar for our resource type. If the type doesn't exist,
# always use :name.
def namevar
if builtin_type? and t = resource_type and t.key_attributes.length == 1
t.key_attributes.first
else
:name
end
end
def extract_parameters(params)
params.each do |param, value|
validate_parameter(param) if strict?
self[param] = value
end
end
def extract_type_and_title(argtype, argtitle)
if (argtype.nil? || argtype == :component || argtype == :whit) &&
argtitle =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
elsif argtitle.nil? && argtype =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
elsif argtitle then [ argtype, argtitle ]
elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ]
elsif argtype.is_a?(Hash) then
raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+
"Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?"
else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference"
end
end
def munge_type_name(value)
return :main if value == :main
return "Class" if value == "" or value.nil? or value.to_s.downcase == "component"
value.to_s.split("::").collect { |s| s.capitalize }.join("::")
end
def parse_title
h = {}
type = resource_type
if type.respond_to? :title_patterns
type.title_patterns.each { |regexp, symbols_and_lambdas|
if captures = regexp.match(title.to_s)
symbols_and_lambdas.zip(captures[1..-1]).each do |symbol_and_lambda,capture|
symbol, proc = symbol_and_lambda
# Many types pass "identity" as the proc; we might as well give
# them a shortcut to delivering that without the extra cost.
#
# Especially because the global type defines title_patterns and
# uses the identity patterns.
#
# This was worth about 8MB of memory allocation saved in my
# testing, so is worth the complexity for the API.
if proc then
h[symbol] = proc.call(capture)
else
h[symbol] = capture
end
end
return h
end
}
# If we've gotten this far, then none of the provided title patterns
# matched. Since there's no way to determine the title then the
# resource should fail here.
raise Puppet::Error, "No set of title patterns matched the title \"#{title}\"."
else
return { :name => title.to_s }
end
end
def parameters
# @parameters could have been loaded from YAML, causing it to be nil (by
# bypassing initialize).
@parameters ||= {}
end
end
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index 832d88eab..0ec2a4a64 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -1,554 +1,554 @@
require 'puppet/node'
require 'puppet/indirector'
require 'puppet/transaction'
require 'puppet/util/pson'
require 'puppet/util/tagging'
require 'puppet/graph'
# This class models a node catalog. It is the thing meant to be passed
# from server to client, and it contains all of the information in the
# catalog, including the resources and the relationships between them.
#
# @api public
class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
class DuplicateResourceError < Puppet::Error
include Puppet::ExternalFileError
end
extend Puppet::Indirector
indirects :catalog, :terminus_setting => :catalog_terminus
include Puppet::Util::Tagging
extend Puppet::Util::Pson
# The host name this is a catalog for.
attr_accessor :name
# The catalog version. Used for testing whether a catalog
# is up to date.
attr_accessor :version
# How long this catalog took to retrieve. Used for reporting stats.
attr_accessor :retrieval_duration
# Whether this is a host catalog, which behaves very differently.
# In particular, reports are sent, graphs are made, and state is
# stored in the state database. If this is set incorrectly, then you often
# end up in infinite loops, because catalogs are used to make things
# that the host catalog needs.
attr_accessor :host_config
# Whether this catalog was retrieved from the cache, which affects
# whether it is written back out again.
attr_accessor :from_cache
# Some metadata to help us compile and generally respond to the current state.
attr_accessor :client_version, :server_version
# A String representing the environment for this catalog
attr_accessor :environment
# The actual environment instance that was used during compilation
attr_accessor :environment_instance
# Add classes to our class list.
def add_class(*classes)
classes.each do |klass|
@classes << klass
end
# Add the class names as tags, too.
tag(*classes)
end
def title_key_for_ref( ref )
ref =~ /^([-\w:]+)\[(.*)\]$/m
[$1, $2]
end
def add_resource(*resources)
resources.each do |resource|
add_one_resource(resource)
end
end
# @param resource [A Resource] a resource in the catalog
# @return [A Resource, nil] the resource that contains the given resource
# @api public
def container_of(resource)
adjacent(resource, :direction => :in)[0]
end
def add_one_resource(resource)
title_key = title_key_for_ref(resource.ref)
if @resource_table[title_key]
fail_on_duplicate_type_and_title(resource, title_key)
end
add_resource_to_table(resource, title_key)
create_resource_aliases(resource)
resource.catalog = self if resource.respond_to?(:catalog=)
add_resource_to_graph(resource)
end
private :add_one_resource
def add_resource_to_table(resource, title_key)
@resource_table[title_key] = resource
@resources << title_key
end
private :add_resource_to_table
def add_resource_to_graph(resource)
add_vertex(resource)
@relationship_graph.add_vertex(resource) if @relationship_graph
end
private :add_resource_to_graph
def create_resource_aliases(resource)
if resource.respond_to?(:isomorphic?) and resource.isomorphic? and resource.name != resource.title
self.alias(resource, resource.uniqueness_key)
end
end
private :create_resource_aliases
# Create an alias for a resource.
def alias(resource, key)
resource.ref =~ /^(.+)\[/
class_name = $1 || resource.class.name
newref = [class_name, key].flatten
if key.is_a? String
ref_string = "#{class_name}[#{key}]"
return if ref_string == resource.ref
end
# LAK:NOTE It's important that we directly compare the references,
# because sometimes an alias is created before the resource is
# added to the catalog, so comparing inside the below if block
# isn't sufficient.
if existing = @resource_table[newref]
return if existing == resource
resource_declaration = " at #{resource.file}:#{resource.line}" if resource.file and resource.line
existing_declaration = " at #{existing.file}:#{existing.line}" if existing.file and existing.line
msg = "Cannot alias #{resource.ref} to #{key.inspect}#{resource_declaration}; resource #{newref.inspect} already declared#{existing_declaration}"
raise ArgumentError, msg
end
@resource_table[newref] = resource
@aliases[resource.ref] ||= []
@aliases[resource.ref] << newref
end
# Apply our catalog to the local host.
# @param options [Hash{Symbol => Object}] a hash of options
# @option options [Puppet::Transaction::Report] :report
# The report object to log this transaction to. This is optional,
# and the resulting transaction will create a report if not
# supplied.
# @option options [Array[String]] :tags
# Tags used to filter the transaction. If supplied then only
# resources tagged with any of these tags will be evaluated.
# @option options [Boolean] :ignoreschedules
# Ignore schedules when evaluating resources
# @option options [Boolean] :for_network_device
# Whether this catalog is for a network device
#
# @return [Puppet::Transaction] the transaction created for this
# application
#
# @api public
def apply(options = {})
Puppet::Util::Storage.load if host_config?
transaction = create_transaction(options)
begin
transaction.report.as_logging_destination do
transaction.evaluate
end
rescue Puppet::Error => detail
Puppet.log_exception(detail, "Could not apply complete catalog: #{detail}")
rescue => detail
Puppet.log_exception(detail, "Got an uncaught exception of type #{detail.class}: #{detail}")
ensure
# Don't try to store state unless we're a host config
# too recursive.
Puppet::Util::Storage.store if host_config?
end
yield transaction if block_given?
transaction
end
# The relationship_graph form of the catalog. This contains all of the
# dependency edges that are used for determining order.
#
# @param given_prioritizer [Puppet::Graph::Prioritizer] The prioritization
# strategy to use when constructing the relationship graph. Defaults the
# being determined by the `ordering` setting.
# @return [Puppet::Graph::RelationshipGraph]
# @api public
def relationship_graph(given_prioritizer = nil)
if @relationship_graph.nil?
@relationship_graph = Puppet::Graph::RelationshipGraph.new(given_prioritizer || prioritizer)
@relationship_graph.populate_from(self)
end
@relationship_graph
end
def clear(remove_resources = true)
super()
# We have to do this so that the resources clean themselves up.
@resource_table.values.each { |resource| resource.remove } if remove_resources
@resource_table.clear
@resources = []
if @relationship_graph
@relationship_graph.clear
@relationship_graph = nil
end
end
def classes
@classes.dup
end
# Create a new resource and register it in the catalog.
def create_resource(type, options)
unless klass = Puppet::Type.type(type)
raise ArgumentError, "Unknown resource type #{type}"
end
return unless resource = klass.new(options)
add_resource(resource)
resource
end
# Make sure all of our resources are "finished".
def finalize
make_default_resources
@resource_table.values.each { |resource| resource.finish }
write_graph(:resources)
end
def host_config?
host_config
end
def initialize(name = nil, environment = Puppet::Node::Environment::NONE)
super()
@name = name
@classes = []
@resource_table = {}
@resources = []
@relationship_graph = nil
@host_config = true
@environment_instance = environment
@environment = environment.to_s
@aliases = {}
if block_given?
yield(self)
finalize
end
end
# Make the default objects necessary for function.
def make_default_resources
# We have to add the resources to the catalog, or else they won't get cleaned up after
# the transaction.
# First create the default scheduling objects
Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) }
# And filebuckets
if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
add_resource(bucket) unless resource(bucket.ref)
end
end
# Remove the resource from our catalog. Notice that we also call
# 'remove' on the resource, at least until resource classes no longer maintain
# references to the resource instances.
def remove_resource(*resources)
resources.each do |resource|
title_key = title_key_for_ref(resource.ref)
@resource_table.delete(title_key)
if aliases = @aliases[resource.ref]
aliases.each { |res_alias| @resource_table.delete(res_alias) }
@aliases.delete(resource.ref)
end
remove_vertex!(resource) if vertex?(resource)
@relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
@resources.delete(title_key)
resource.remove
end
end
# Look a resource up by its reference (e.g., File[/etc/passwd]).
def resource(type, title = nil)
# Always create a resource reference, so that it always
# canonicalizes how we are referring to them.
if title
res = Puppet::Resource.new(type, title)
else
# If they didn't provide a title, then we expect the first
# argument to be of the form 'Class[name]', which our
# Reference class canonicalizes for us.
res = Puppet::Resource.new(nil, type)
end
res.catalog = self
title_key = [res.type, res.title.to_s]
uniqueness_key = [res.type, res.uniqueness_key].flatten
@resource_table[title_key] || @resource_table[uniqueness_key]
end
def resource_refs
resource_keys.collect{ |type, name| name.is_a?( String ) ? "#{type}[#{name}]" : nil}.compact
end
def resource_keys
@resource_table.keys
end
def resources
@resources.collect do |key|
@resource_table[key]
end
end
def self.from_data_hash(data)
result = new(data['name'], Puppet::Node::Environment::NONE)
if tags = data['tags']
result.tag(*tags)
end
if version = data['version']
result.version = version
end
if environment = data['environment']
result.environment = environment
result.environment_instance = Puppet::Node::Environment.remote(environment.to_sym)
end
if resources = data['resources']
result.add_resource(*resources.collect do |res|
Puppet::Resource.from_data_hash(res)
end)
end
if edges = data['edges']
edges.each do |edge_hash|
edge = Puppet::Relationship.from_data_hash(edge_hash)
unless source = result.resource(edge.source)
- raise ArgumentError, "Could not intern from data: Could not find relationship source #{edge.source.inspect}"
+ raise ArgumentError, "Could not intern from data: Could not find relationship source #{edge.source.inspect} for #{edge.target.to_s}"
end
edge.source = source
unless target = result.resource(edge.target)
- raise ArgumentError, "Could not intern from data: Could not find relationship target #{edge.target.inspect}"
+ raise ArgumentError, "Could not intern from data: Could not find relationship target #{edge.target.inspect} for #{edge.source.to_s}"
end
edge.target = target
result.add_edge(edge)
end
end
if classes = data['classes']
result.add_class(*classes)
end
result
end
def self.from_pson(data)
Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.")
self.from_data_hash(data)
end
def to_data_hash
{
'tags' => tags,
'name' => name,
'version' => version,
'environment' => environment.to_s,
'resources' => @resources.collect { |v| @resource_table[v].to_pson_data_hash },
'edges' => edges. collect { |e| e.to_pson_data_hash },
'classes' => classes
}
end
PSON.register_document_type('Catalog',self)
def to_pson_data_hash
{
'document_type' => 'Catalog',
'data' => to_data_hash,
'metadata' => {
'api_version' => 1
}
}
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Convert our catalog into a RAL catalog.
def to_ral
to_catalog :to_ral
end
# Convert our catalog into a catalog of Puppet::Resource instances.
def to_resource
to_catalog :to_resource
end
# filter out the catalog, applying +block+ to each resource.
# If the block result is false, the resource will
# be kept otherwise it will be skipped
def filter(&block)
to_catalog :to_resource, &block
end
# Store the classes in the classfile.
def write_class_file
::File.open(Puppet[:classfile], "w") do |f|
f.puts classes.join("\n")
end
rescue => detail
Puppet.err "Could not create class file #{Puppet[:classfile]}: #{detail}"
end
# Store the list of resources we manage
def write_resource_file
::File.open(Puppet[:resourcefile], "w") do |f|
to_print = resources.map do |resource|
next unless resource.managed?
if resource.name_var
"#{resource.type}[#{resource[resource.name_var]}]"
else
"#{resource.ref.downcase}"
end
end.compact
f.puts to_print.join("\n")
end
rescue => detail
Puppet.err "Could not create resource file #{Puppet[:resourcefile]}: #{detail}"
end
# Produce the graph files if requested.
def write_graph(name)
# We only want to graph the main host catalog.
return unless host_config?
super
end
private
def prioritizer
@prioritizer ||= case Puppet[:ordering]
when "title-hash"
Puppet::Graph::TitleHashPrioritizer.new
when "manifest"
Puppet::Graph::SequentialPrioritizer.new
when "random"
Puppet::Graph::RandomPrioritizer.new
else
raise Puppet::DevError, "Unknown ordering type #{Puppet[:ordering]}"
end
end
def create_transaction(options)
transaction = Puppet::Transaction.new(self, options[:report], prioritizer)
transaction.tags = options[:tags] if options[:tags]
transaction.ignoreschedules = true if options[:ignoreschedules]
transaction.for_network_device = options[:network_device]
transaction
end
# Verify that the given resource isn't declared elsewhere.
def fail_on_duplicate_type_and_title(resource, title_key)
# Short-circuit the common case,
return unless existing_resource = @resource_table[title_key]
# If we've gotten this far, it's a real conflict
msg = "Duplicate declaration: #{resource.ref} is already declared"
msg << " in file #{existing_resource.file}:#{existing_resource.line}" if existing_resource.file and existing_resource.line
msg << "; cannot redeclare"
raise DuplicateResourceError.new(msg, resource.file, resource.line)
end
# An abstracted method for converting one catalog into another type of catalog.
# This pretty much just converts all of the resources from one class to another, using
# a conversion method.
def to_catalog(convert)
result = self.class.new(self.name, self.environment_instance)
result.version = self.version
map = {}
resources.each do |resource|
next if virtual_not_exported?(resource)
next if block_given? and yield resource
newres = resource.copy_as_resource
newres.catalog = result
if convert != :to_resource
newres = newres.to_ral
end
# We can't guarantee that resources don't munge their names
# (like files do with trailing slashes), so we have to keep track
# of what a resource got converted to.
map[resource.ref] = newres
result.add_resource newres
end
message = convert.to_s.gsub "_", " "
edges.each do |edge|
# Skip edges between virtual resources.
next if virtual_not_exported?(edge.source)
next if block_given? and yield edge.source
next if virtual_not_exported?(edge.target)
next if block_given? and yield edge.target
unless source = map[edge.source.ref]
raise Puppet::DevError, "Could not find resource #{edge.source.ref} when converting #{message} resources"
end
unless target = map[edge.target.ref]
raise Puppet::DevError, "Could not find resource #{edge.target.ref} when converting #{message} resources"
end
result.add_edge(source, target, edge.label)
end
map.clear
result.add_class(*self.classes)
result.tag(*self.tags)
result
end
def virtual_not_exported?(resource)
resource.virtual && !resource.exported
end
end
diff --git a/lib/puppet/settings.rb b/lib/puppet/settings.rb
index 3201b125f..499ee1502 100644
--- a/lib/puppet/settings.rb
+++ b/lib/puppet/settings.rb
@@ -1,1392 +1,1420 @@
require 'puppet'
require 'getoptlong'
require 'puppet/util/watched_file'
require 'puppet/util/command_line/puppet_option_parser'
require 'forwardable'
# The class for handling configuration files.
class Puppet::Settings
extend Forwardable
include Enumerable
require 'puppet/settings/errors'
require 'puppet/settings/base_setting'
require 'puppet/settings/string_setting'
require 'puppet/settings/enum_setting'
require 'puppet/settings/array_setting'
require 'puppet/settings/file_setting'
require 'puppet/settings/directory_setting'
require 'puppet/settings/file_or_directory_setting'
require 'puppet/settings/path_setting'
require 'puppet/settings/boolean_setting'
require 'puppet/settings/terminus_setting'
require 'puppet/settings/duration_setting'
require 'puppet/settings/ttl_setting'
require 'puppet/settings/priority_setting'
require 'puppet/settings/autosign_setting'
require 'puppet/settings/config_file'
require 'puppet/settings/value_translator'
require 'puppet/settings/environment_conf'
# local reference for convenience
PuppetOptionParser = Puppet::Util::CommandLine::PuppetOptionParser
attr_accessor :files
attr_reader :timer
# These are the settings that every app is required to specify; there are reasonable defaults defined in application.rb.
REQUIRED_APP_SETTINGS = [:logdir, :confdir, :vardir]
# This method is intended for puppet internal use only; it is a convenience method that
# returns reasonable application default settings values for a given run_mode.
def self.app_defaults_for_run_mode(run_mode)
{
:name => run_mode.to_s,
:run_mode => run_mode.name,
:confdir => run_mode.conf_dir,
:vardir => run_mode.var_dir,
:rundir => run_mode.run_dir,
:logdir => run_mode.log_dir,
}
end
def self.default_certname()
hostname = hostname_fact
domain = domain_fact
if domain and domain != ""
fqdn = [hostname, domain].join(".")
else
fqdn = hostname
end
fqdn.to_s.gsub(/\.$/, '')
end
def self.hostname_fact()
Facter["hostname"].value
end
def self.domain_fact()
Facter["domain"].value
end
def self.default_config_file_name
"puppet.conf"
end
# Create a new collection of config settings.
def initialize
@config = {}
@shortnames = {}
@created = []
# Keep track of set values.
@value_sets = {
:cli => Values.new(:cli, @config),
:memory => Values.new(:memory, @config),
:application_defaults => Values.new(:application_defaults, @config),
:overridden_defaults => Values.new(:overridden_defaults, @config),
}
@configuration_file = nil
# And keep a per-environment cache
@cache = Hash.new { |hash, key| hash[key] = {} }
@values = Hash.new { |hash, key| hash[key] = {} }
# The list of sections we've used.
@used = []
@hooks_to_call_on_application_initialization = []
@deprecated_setting_names = []
@deprecated_settings_that_have_been_configured = []
@translate = Puppet::Settings::ValueTranslator.new
@config_file_parser = Puppet::Settings::ConfigFile.new(@translate)
end
# @param name [Symbol] The name of the setting to fetch
# @return [Puppet::Settings::BaseSetting] The setting object
def setting(name)
@config[name]
end
# Retrieve a config value
# @param param [Symbol] the name of the setting
# @return [Object] the value of the setting
# @api private
def [](param)
if @deprecated_setting_names.include?(param)
issue_deprecation_warning(setting(param), "Accessing '#{param}' as a setting is deprecated.")
end
value(param)
end
# Set a config value. This doesn't set the defaults, it sets the value itself.
# @param param [Symbol] the name of the setting
# @param value [Object] the new value of the setting
# @api private
def []=(param, value)
if @deprecated_setting_names.include?(param)
issue_deprecation_warning(setting(param), "Modifying '#{param}' as a setting is deprecated.")
end
@value_sets[:memory].set(param, value)
unsafe_flush_cache
end
# Create a new default value for the given setting. The default overrides are
# higher precedence than the defaults given in defaults.rb, but lower
# precedence than any other values for the setting. This allows one setting
# `a` to change the default of setting `b`, but still allow a user to provide
# a value for setting `b`.
#
# @param param [Symbol] the name of the setting
# @param value [Object] the new default value for the setting
# @api private
def override_default(param, value)
@value_sets[:overridden_defaults].set(param, value)
unsafe_flush_cache
end
# Generate the list of valid arguments, in a format that GetoptLong can
# understand, and add them to the passed option list.
def addargs(options)
# Add all of the settings as valid options.
self.each { |name, setting|
setting.getopt_args.each { |args| options << args }
}
options
end
# Generate the list of valid arguments, in a format that OptionParser can
# understand, and add them to the passed option list.
def optparse_addargs(options)
# Add all of the settings as valid options.
self.each { |name, setting|
options << setting.optparse_args
}
options
end
# Is our setting a boolean setting?
def boolean?(param)
param = param.to_sym
@config.include?(param) and @config[param].kind_of?(BooleanSetting)
end
# Remove all set values, potentially skipping cli values.
def clear
unsafe_clear
end
# Remove all set values, potentially skipping cli values.
def unsafe_clear(clear_cli = true, clear_application_defaults = false)
if clear_application_defaults
@value_sets[:application_defaults] = Values.new(:application_defaults, @config)
@app_defaults_initialized = false
end
if clear_cli
@value_sets[:cli] = Values.new(:cli, @config)
# Only clear the 'used' values if we were explicitly asked to clear out
# :cli values; otherwise, it may be just a config file reparse,
# and we want to retain this cli values.
@used = []
end
@value_sets[:memory] = Values.new(:memory, @config)
@value_sets[:overridden_defaults] = Values.new(:overridden_defaults, @config)
@deprecated_settings_that_have_been_configured.clear
@values.clear
@cache.clear
end
private :unsafe_clear
# Clear @cache, @used and the Environment.
#
# Whenever an object is returned by Settings, a copy is stored in @cache.
# As long as Setting attributes that determine the content of returned
# objects remain unchanged, Settings can keep returning objects from @cache
# without re-fetching or re-generating them.
#
# Whenever a Settings attribute changes, such as @values or @preferred_run_mode,
# this method must be called to clear out the caches so that updated
# objects will be returned.
def flush_cache
unsafe_flush_cache
end
def unsafe_flush_cache
clearused
# Clear the list of environments, because they cache, at least, the module path.
# We *could* preferentially just clear them if the modulepath is changed,
# but we don't really know if, say, the vardir is changed and the modulepath
# is defined relative to it. We need the defined?(stuff) because of loading
# order issues.
Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment)
end
private :unsafe_flush_cache
def clearused
@cache.clear
@used = []
end
def global_defaults_initialized?()
@global_defaults_initialized
end
def initialize_global_settings(args = [])
raise Puppet::DevError, "Attempting to initialize global default settings more than once!" if global_defaults_initialized?
# The first two phases of the lifecycle of a puppet application are:
# 1) Parse the command line options and handle any of them that are
# registered, defined "global" puppet settings (mostly from defaults.rb).
# 2) Parse the puppet config file(s).
parse_global_options(args)
parse_config_files
@global_defaults_initialized = true
end
# This method is called during application bootstrapping. It is responsible for parsing all of the
# command line options and initializing the settings accordingly.
#
# It will ignore options that are not defined in the global puppet settings list, because they may
# be valid options for the specific application that we are about to launch... however, at this point
# in the bootstrapping lifecycle, we don't yet know what that application is.
def parse_global_options(args)
# Create an option parser
option_parser = PuppetOptionParser.new
option_parser.ignore_invalid_options = true
# Add all global options to it.
self.optparse_addargs([]).each do |option|
option_parser.on(*option) do |arg|
opt, val = Puppet::Settings.clean_opt(option[0], arg)
handlearg(opt, val)
end
end
option_parser.on('--run_mode',
"The effective 'run mode' of the application: master, agent, or user.",
:REQUIRED) do |arg|
Puppet.settings.preferred_run_mode = arg
end
option_parser.parse(args)
# remove run_mode options from the arguments so that later parses don't think
# it is an unknown option.
while option_index = args.index('--run_mode') do
args.delete_at option_index
args.delete_at option_index
end
args.reject! { |arg| arg.start_with? '--run_mode=' }
end
private :parse_global_options
# A utility method (public, is used by application.rb and perhaps elsewhere) that munges a command-line
# option string into the format that Puppet.settings expects. (This mostly has to deal with handling the
# "no-" prefix on flag/boolean options).
#
# @param [String] opt the command line option that we are munging
# @param [String, TrueClass, FalseClass] val the value for the setting (as determined by the OptionParser)
def self.clean_opt(opt, val)
# rewrite --[no-]option to --no-option if that's what was given
if opt =~ /\[no-\]/ and !val
opt = opt.gsub(/\[no-\]/,'no-')
end
# otherwise remove the [no-] prefix to not confuse everybody
opt = opt.gsub(/\[no-\]/, '')
[opt, val]
end
def app_defaults_initialized?
@app_defaults_initialized
end
def initialize_app_defaults(app_defaults)
REQUIRED_APP_SETTINGS.each do |key|
raise SettingsError, "missing required app default setting '#{key}'" unless app_defaults.has_key?(key)
end
app_defaults.each do |key, value|
if key == :run_mode
self.preferred_run_mode = value
else
@value_sets[:application_defaults].set(key, value)
unsafe_flush_cache
end
end
apply_metadata
call_hooks_deferred_to_application_initialization
issue_deprecations
@app_defaults_initialized = true
end
def call_hooks_deferred_to_application_initialization(options = {})
@hooks_to_call_on_application_initialization.each do |setting|
begin
setting.handle(self.value(setting.name))
rescue InterpolationError => err
raise InterpolationError, err, err.backtrace unless options[:ignore_interpolation_dependency_errors]
#swallow. We're not concerned if we can't call hooks because dependencies don't exist yet
#we'll get another chance after application defaults are initialized
end
end
end
private :call_hooks_deferred_to_application_initialization
# Return a value's description.
def description(name)
if obj = @config[name.to_sym]
obj.desc
else
nil
end
end
def_delegator :@config, :each
# Iterate over each section name.
def eachsection
yielded = []
@config.each do |name, object|
section = object.section
unless yielded.include? section
yield section
yielded << section
end
end
end
# Return an object by name.
def setting(param)
param = param.to_sym
@config[param]
end
# Handle a command-line argument.
def handlearg(opt, value = nil)
@cache.clear
if value.is_a?(FalseClass)
value = "false"
elsif value.is_a?(TrueClass)
value = "true"
end
value &&= @translate[value]
str = opt.sub(/^--/,'')
bool = true
newstr = str.sub(/^no-/, '')
if newstr != str
str = newstr
bool = false
end
str = str.intern
if @config[str].is_a?(Puppet::Settings::BooleanSetting)
if value == "" or value.nil?
value = bool
end
end
if s = @config[str]
@deprecated_settings_that_have_been_configured << s if s.completely_deprecated?
end
@value_sets[:cli].set(str, value)
unsafe_flush_cache
end
def include?(name)
name = name.intern if name.is_a? String
@config.include?(name)
end
# check to see if a short name is already defined
def shortinclude?(short)
short = short.intern if name.is_a? String
@shortnames.include?(short)
end
# Prints the contents of a config file with the available config settings, or it
# prints a single value of a config setting.
def print_config_options
env = value(:environment)
val = value(:configprint)
if val == "all"
hash = {}
each do |name, obj|
val = value(name,env)
val = val.inspect if val == ""
hash[name] = val
end
hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val|
puts "#{name} = #{val}"
end
else
val.split(/\s*,\s*/).sort.each do |v|
if include?(v)
#if there is only one value, just print it for back compatibility
if v == val
puts value(val,env)
break
end
puts "#{v} = #{value(v,env)}"
else
puts "invalid setting: #{v}"
return false
end
end
end
true
end
def generate_config
puts to_config
true
end
def generate_manifest
puts to_manifest
true
end
def print_configs
return print_config_options if value(:configprint) != ""
return generate_config if value(:genconfig)
generate_manifest if value(:genmanifest)
end
def print_configs?
(value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true
end
# Return a given object's file metadata.
def metadata(param)
if obj = @config[param.to_sym] and obj.is_a?(FileSetting)
{
:owner => obj.owner,
:group => obj.group,
:mode => obj.mode
}.delete_if { |key, value| value.nil? }
else
nil
end
end
# Make a directory with the appropriate user, group, and mode
def mkdir(default)
obj = get_config_file_default(default)
Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do
mode = obj.mode || 0750
Dir.mkdir(obj.value, mode)
end
end
# The currently configured run mode that is preferred for constructing the application configuration.
def preferred_run_mode
@preferred_run_mode_name || :user
end
# PRIVATE! This only exists because we need a hook to validate the run mode when it's being set, and
# it should never, ever, ever, ever be called from outside of this file.
# This method is also called when --run_mode MODE is used on the command line to set the default
#
# @param mode [String|Symbol] the name of the mode to have in effect
# @api private
def preferred_run_mode=(mode)
mode = mode.to_s.downcase.intern
raise ValidationError, "Invalid run mode '#{mode}'" unless [:master, :agent, :user].include?(mode)
@preferred_run_mode_name = mode
# Changing the run mode has far-reaching consequences. Flush any cached
# settings so they will be re-generated.
flush_cache
mode
end
# Return all of the settings associated with a given section.
def params(section = nil)
if section
section = section.intern if section.is_a? String
@config.find_all { |name, obj|
obj.section == section
}.collect { |name, obj|
name
}
else
@config.keys
end
end
def parse_config(text, file = "text")
begin
data = @config_file_parser.parse_file(file, text)
rescue => detail
Puppet.log_exception(detail, "Could not parse #{file}: #{detail}")
return
end
# If we get here and don't have any data, we just return and don't muck with the current state of the world.
return if data.nil?
# If we get here then we have some data, so we need to clear out any
# previous settings that may have come from config files.
unsafe_clear(false, false)
record_deprecations_from_puppet_conf(data)
# And now we can repopulate with the values from our last parsing of the config files.
@configuration_file = data
# Determine our environment, if we have one.
if @config[:environment]
env = self.value(:environment).to_sym
else
env = "none"
end
# Call any hooks we should be calling.
@config.values.select(&:has_hook?).each do |setting|
value_sets_for(env, self.preferred_run_mode).each do |source|
if source.include?(setting.name)
# We still have to use value to retrieve the value, since
# we want the fully interpolated value, not $vardir/lib or whatever.
# This results in extra work, but so few of the settings
# will have associated hooks that it ends up being less work this
# way overall.
if setting.call_hook_on_initialize?
@hooks_to_call_on_application_initialization << setting
else
setting.handle(self.value(setting.name, env))
end
break
end
end
end
call_hooks_deferred_to_application_initialization :ignore_interpolation_dependency_errors => true
apply_metadata
end
# Parse the configuration file. Just provides thread safety.
def parse_config_files
file = which_configuration_file
if Puppet::FileSystem.exist?(file)
begin
text = read_file(file)
rescue => detail
Puppet.log_exception(detail, "Could not load #{file}: #{detail}")
return
end
else
return
end
parse_config(text, file)
end
private :parse_config_files
def main_config_file
if explicit_config_file?
return self[:config]
else
return File.join(Puppet::Util::RunMode[:master].conf_dir, config_file_name)
end
end
private :main_config_file
def user_config_file
return File.join(Puppet::Util::RunMode[:user].conf_dir, config_file_name)
end
private :user_config_file
# This method is here to get around some life-cycle issues. We need to be
# able to determine the config file name before the settings / defaults are
# fully loaded. However, we also need to respect any overrides of this value
# that the user may have specified on the command line.
#
# The easiest way to do this is to attempt to read the setting, and if we
# catch an error (meaning that it hasn't been set yet), we'll fall back to
# the default value.
def config_file_name
begin
return self[:config_file_name] if self[:config_file_name]
rescue SettingsError
# This just means that the setting wasn't explicitly set on the command line, so we will ignore it and
# fall through to the default name.
end
return self.class.default_config_file_name
end
private :config_file_name
def apply_metadata
# We have to do it in the reverse of the search path,
# because multiple sections could set the same value
# and I'm too lazy to only set the metadata once.
if @configuration_file
searchpath.reverse.each do |source|
source = preferred_run_mode if source == :run_mode
if section = @configuration_file.sections[source]
apply_metadata_from_section(section)
end
end
end
end
private :apply_metadata
def apply_metadata_from_section(section)
section.settings.each do |setting|
if setting.has_metadata? && type = @config[setting.name]
type.set_meta(setting.meta)
end
end
end
SETTING_TYPES = {
:string => StringSetting,
:file => FileSetting,
:directory => DirectorySetting,
:file_or_directory => FileOrDirectorySetting,
:path => PathSetting,
:boolean => BooleanSetting,
:terminus => TerminusSetting,
:duration => DurationSetting,
:ttl => TTLSetting,
:array => ArraySetting,
:enum => EnumSetting,
:priority => PrioritySetting,
:autosign => AutosignSetting,
}
# Create a new setting. The value is passed in because it's used to determine
# what kind of setting we're creating, but the value itself might be either
# a default or a value, so we can't actually assign it.
#
# See #define_settings for documentation on the legal values for the ":type" option.
def newsetting(hash)
klass = nil
hash[:section] = hash[:section].to_sym if hash[:section]
if type = hash[:type]
unless klass = SETTING_TYPES[type]
raise ArgumentError, "Invalid setting type '#{type}'"
end
hash.delete(:type)
else
# The only implicit typing we still do for settings is to fall back to "String" type if they didn't explicitly
# specify a type. Personally I'd like to get rid of this too, and make the "type" option mandatory... but
# there was a little resistance to taking things quite that far for now. --cprice 2012-03-19
klass = StringSetting
end
hash[:settings] = self
setting = klass.new(hash)
setting
end
# This has to be private, because it doesn't add the settings to @config
private :newsetting
# Iterate across all of the objects in a given section.
def persection(section)
section = section.to_sym
self.each { |name, obj|
if obj.section == section
yield obj
end
}
end
# Reparse our config file, if necessary.
def reparse_config_files
if files
if filename = any_files_changed?
Puppet.notice "Config file #{filename} changed; triggering re-parse of all config files."
parse_config_files
reuse
end
end
end
def files
return @files if @files
@files = []
[main_config_file, user_config_file].each do |path|
if Puppet::FileSystem.exist?(path)
@files << Puppet::Util::WatchedFile.new(path)
end
end
@files
end
private :files
# Checks to see if any of the config files have been modified
# @return the filename of the first file that is found to have changed, or
# nil if no files have changed
def any_files_changed?
files.each do |file|
return file.to_str if file.changed?
end
nil
end
private :any_files_changed?
def reuse
return unless defined?(@used)
new = @used
@used = []
self.use(*new)
end
# The order in which to search for values.
def searchpath(environment = nil)
[:memory, :cli, environment, :run_mode, :main, :application_defaults, :overridden_defaults].compact
end
# Get a list of objects per section
def sectionlist
sectionlist = []
self.each { |name, obj|
section = obj.section || "puppet"
sections[section] ||= []
sectionlist << section unless sectionlist.include?(section)
sections[section] << obj
}
return sectionlist, sections
end
def service_user_available?
return @service_user_available if defined?(@service_user_available)
if self[:user]
user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure
@service_user_available = user.exists?
else
@service_user_available = false
end
end
def service_group_available?
return @service_group_available if defined?(@service_group_available)
if self[:group]
group = Puppet::Type.type(:group).new :name => self[:group], :audit => :ensure
@service_group_available = group.exists?
else
@service_group_available = false
end
end
# Allow later inspection to determine if the setting was set on the
# command line, or through some other code path. Used for the
# `dns_alt_names` option during cert generate. --daniel 2011-10-18
def set_by_cli?(param)
param = param.to_sym
!@value_sets[:cli].lookup(param).nil?
end
def set_value(param, value, type, options = {})
Puppet.deprecation_warning("Puppet.settings.set_value is deprecated. Use Puppet[]= instead.")
if @value_sets[type]
@value_sets[type].set(param, value)
unsafe_flush_cache
end
end
# Deprecated; use #define_settings instead
def setdefaults(section, defs)
Puppet.deprecation_warning("'setdefaults' is deprecated and will be removed; please call 'define_settings' instead")
define_settings(section, defs)
end
# Define a group of settings.
#
# @param [Symbol] section a symbol to use for grouping multiple settings together into a conceptual unit. This value
# (and the conceptual separation) is not used very often; the main place where it will have a potential impact
# is when code calls Settings#use method. See docs on that method for further details, but basically that method
# just attempts to do any preparation that may be necessary before code attempts to leverage the value of a particular
# setting. This has the most impact for file/directory settings, where #use will attempt to "ensure" those
# files / directories.
# @param [Hash[Hash]] defs the settings to be defined. This argument is a hash of hashes; each key should be a symbol,
# which is basically the name of the setting that you are defining. The value should be another hash that specifies
# the parameters for the particular setting. Legal values include:
# [:default] => not required; this is the value for the setting if no other value is specified (via cli, config file, etc.)
# For string settings this may include "variables", demarcated with $ or ${} which will be interpolated with values of other settings.
# The default value may also be a Proc that will be called only once to evaluate the default when the setting's value is retrieved.
# [:desc] => required; a description of the setting, used in documentation / help generation
# [:type] => not required, but highly encouraged! This specifies the data type that the setting represents. If
# you do not specify it, it will default to "string". Legal values include:
# :string - A generic string setting
# :boolean - A boolean setting; values are expected to be "true" or "false"
# :file - A (single) file path; puppet may attempt to create this file depending on how the settings are used. This type
# also supports additional options such as "mode", "owner", "group"
# :directory - A (single) directory path; puppet may attempt to create this file depending on how the settings are used. This type
# also supports additional options such as "mode", "owner", "group"
# :path - This is intended to be used for settings whose value can contain multiple directory paths, respresented
# as strings separated by the system path separator (e.g. system path, module path, etc.).
# [:mode] => an (optional) octal value to be used as the permissions/mode for :file and :directory settings
# [:owner] => optional owner username/uid for :file and :directory settings
# [:group] => optional group name/gid for :file and :directory settings
#
def define_settings(section, defs)
section = section.to_sym
call = []
defs.each do |name, hash|
raise ArgumentError, "setting definition for '#{name}' is not a hash!" unless hash.is_a? Hash
name = name.to_sym
hash[:name] = name
hash[:section] = section
raise ArgumentError, "Setting #{name} is already defined" if @config.include?(name)
tryconfig = newsetting(hash)
if short = tryconfig.short
if other = @shortnames[short]
raise ArgumentError, "Setting #{other.name} is already using short name '#{short}'"
end
@shortnames[short] = tryconfig
end
@config[name] = tryconfig
# Collect the settings that need to have their hooks called immediately.
# We have to collect them so that we can be sure we're fully initialized before
# the hook is called.
if tryconfig.has_hook?
if tryconfig.call_hook_on_define?
call << tryconfig
elsif tryconfig.call_hook_on_initialize?
@hooks_to_call_on_application_initialization << tryconfig
end
end
@deprecated_setting_names << name if tryconfig.deprecated?
end
call.each do |setting|
setting.handle(self.value(setting.name))
end
end
# Convert the settings we manage into a catalog full of resources that model those settings.
def to_catalog(*sections)
sections = nil if sections.empty?
catalog = Puppet::Resource::Catalog.new("Settings", Puppet::Node::Environment::NONE)
-
@config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key|
+ next if (key == :manifestdir && should_skip_manifestdir?())
file = @config[key]
next unless (sections.nil? or sections.include?(file.section))
next unless resource = file.to_resource
next if catalog.resource(resource.ref)
Puppet.debug("Using settings: adding file resource '#{key}': '#{resource.inspect}'")
catalog.add_resource(resource)
end
add_user_resources(catalog, sections)
add_environment_resources(catalog, sections)
catalog
end
+ def should_skip_manifestdir?()
+ setting = @config[:environmentpath]
+ !(setting.nil? || setting.value.nil? || setting.value.empty?)
+ end
+
+ private :should_skip_manifestdir?
+
# Convert our list of config settings into a configuration file.
def to_config
str = %{The configuration file for #{Puppet.run_mode.name}. Note that this file
is likely to have unused settings in it; any setting that's
valid anywhere in Puppet can be in any config file, even if it's not used.
Every section can specify three special parameters: owner, group, and mode.
These parameters affect the required permissions of any files specified after
their specification. Puppet will sometimes use these parameters to check its
own configured state, so they can be used to make Puppet a bit more self-managing.
The file format supports octothorpe-commented lines, but not partial-line comments.
Generated on #{Time.now}.
}.gsub(/^/, "# ")
# Add a section heading that matches our name.
str += "[#{preferred_run_mode}]\n"
eachsection do |section|
persection(section) do |obj|
str += obj.to_config + "\n" unless obj.name == :genconfig
end
end
return str
end
# Convert to a parseable manifest
def to_manifest
catalog = to_catalog
catalog.resource_refs.collect do |ref|
catalog.resource(ref).to_manifest
end.join("\n\n")
end
# Create the necessary objects to use a section. This is idempotent;
# you can 'use' a section as many times as you want.
def use(*sections)
sections = sections.collect { |s| s.to_sym }
sections = sections.reject { |s| @used.include?(s) }
return if sections.empty?
begin
catalog = to_catalog(*sections).to_ral
rescue => detail
Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}")
end
catalog.host_config = false
catalog.apply do |transaction|
if transaction.any_failed?
report = transaction.report
status_failures = report.resource_statuses.values.select { |r| r.failed? }
status_fail_msg = status_failures.
collect(&:events).
flatten.
select { |event| event.status == 'failure' }.
collect { |event| "#{event.resource}: #{event.message}" }.join("; ")
raise "Got #{status_failures.length} failure(s) while initializing: #{status_fail_msg}"
end
end
sections.each { |s| @used << s }
@used.uniq!
end
def valid?(param)
param = param.to_sym
@config.has_key?(param)
end
def uninterpolated_value(param, environment = nil)
Puppet.deprecation_warning("Puppet.settings.uninterpolated_value is deprecated. Use Puppet.settings.value instead")
param = param.to_sym
environment &&= environment.to_sym
values(environment, self.preferred_run_mode).lookup(param)
end
# Retrieve an object that can be used for looking up values of configuration
# settings.
#
# @param environment [Symbol] The name of the environment in which to lookup
# @param section [Symbol] The name of the configuration section in which to lookup
# @return [Puppet::Settings::ChainedValues] An object to perform lookups
# @api public
def values(environment, section)
@values[environment][section] ||= ChainedValues.new(
section,
environment,
value_sets_for(environment, section),
@config)
end
# Find the correct value using our search path.
#
# @param param [String, Symbol] The value to look up
# @param environment [String, Symbol] The environment to check for the value
# @param bypass_interpolation [true, false] Whether to skip interpolation
#
# @return [Object] The looked up value
#
# @raise [InterpolationError]
def value(param, environment = nil, bypass_interpolation = false)
param = param.to_sym
environment &&= environment.to_sym
setting = @config[param]
# Short circuit to nil for undefined settings.
return nil if setting.nil?
# Check the cache first. It needs to be a per-environment
# cache so that we don't spread values from one env
# to another.
if @cache[environment||"none"].has_key?(param)
return @cache[environment||"none"][param]
elsif bypass_interpolation
val = values(environment, self.preferred_run_mode).lookup(param)
else
val = values(environment, self.preferred_run_mode).interpolate(param)
end
@cache[environment||"none"][param] = val
val
end
##
# (#15337) All of the logic to determine the configuration file to use
# should be centralized into this method. The simplified approach is:
#
# 1. If there is an explicit configuration file, use that. (--confdir or
# --config)
# 2. If we're running as a root process, use the system puppet.conf
# (usually /etc/puppet/puppet.conf)
# 3. Otherwise, use the user puppet.conf (usually ~/.puppet/puppet.conf)
#
# @api private
# @todo this code duplicates {Puppet::Util::RunMode#which_dir} as described
# in {http://projects.puppetlabs.com/issues/16637 #16637}
def which_configuration_file
if explicit_config_file? or Puppet.features.root? then
return main_config_file
else
return user_config_file
end
end
# This method just turns a file into a new ConfigFile::Conf instance
# @param file [String] absolute path to the configuration file
# @return [Puppet::Settings::ConfigFile::Conf]
# @api private
def parse_file(file)
@config_file_parser.parse_file(file, read_file(file))
end
private
DEPRECATION_REFS = {
[:manifest, :modulepath, :config_version, :templatedir, :manifestdir] =>
"See http://links.puppetlabs.com/env-settings-deprecations"
}.freeze
# Record that we want to issue a deprecation warning later in the application
# initialization cycle when we have settings bootstrapped to the point where
# we can read the Puppet[:disable_warnings] setting.
#
# We are only recording warnings applicable to settings set in puppet.conf
# itself.
def record_deprecations_from_puppet_conf(puppet_conf)
conf_sections = puppet_conf.sections.inject([]) do |accum,entry|
accum << entry[1] if [:main, :master, :agent, :user].include?(entry[0])
accum
end
conf_sections.each do |section|
section.settings.each do |conf_setting|
if setting = self.setting(conf_setting.name)
@deprecated_settings_that_have_been_configured << setting if setting.deprecated?
end
end
end
end
def issue_deprecations
@deprecated_settings_that_have_been_configured.each do |setting|
issue_deprecation_warning(setting)
end
end
def issue_deprecation_warning(setting, msg = nil)
name = setting.name
ref = DEPRECATION_REFS.find { |params,reference| params.include?(name) }
ref = ref[1] if ref
case
when msg
msg << " #{ref}" if ref
Puppet.deprecation_warning(msg)
when setting.completely_deprecated?
Puppet.deprecation_warning("Setting #{name} is deprecated. #{ref}", "setting-#{name}")
when setting.allowed_on_commandline?
Puppet.deprecation_warning("Setting #{name} is deprecated in puppet.conf. #{ref}", "puppet-conf-setting-#{name}")
end
end
def get_config_file_default(default)
obj = nil
unless obj = @config[default]
raise ArgumentError, "Unknown default #{default}"
end
raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting
obj
end
def add_environment_resources(catalog, sections)
path = self[:environmentpath]
envdir = path.split(File::PATH_SEPARATOR).first if path
configured_environment = self[:environment]
if configured_environment == "production" && envdir && Puppet::FileSystem.exist?(envdir)
configured_environment_path = File.join(envdir, configured_environment)
- catalog.add_resource(
- Puppet::Resource.new(:file,
- configured_environment_path,
- :parameters => { :ensure => 'directory' })
- )
+ if !Puppet::FileSystem.symlink?(configured_environment_path)
+ catalog.add_resource(
+ Puppet::Resource.new(:file,
+ configured_environment_path,
+ :parameters => { :ensure => 'directory' })
+ )
+ end
end
end
def add_user_resources(catalog, sections)
return unless Puppet.features.root?
return if Puppet.features.microsoft_windows?
return unless self[:mkusers]
@config.each do |name, setting|
next unless setting.respond_to?(:owner)
next unless sections.nil? or sections.include?(setting.section)
if user = setting.owner and user != "root" and catalog.resource(:user, user).nil?
resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present})
resource[:gid] = self[:group] if self[:group]
catalog.add_resource resource
end
if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil?
catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present})
end
end
end
# Yield each search source in turn.
def value_sets_for(environment, mode)
searchpath(environment).collect do |name|
case name
when :cli, :memory, :application_defaults, :overridden_defaults
@value_sets[name]
when :run_mode
if @configuration_file
section = @configuration_file.sections[mode]
if section
ValuesFromSection.new(mode, section)
end
end
else
values_from_section = nil
if @configuration_file
if section = @configuration_file.sections[name]
values_from_section = ValuesFromSection.new(name, section)
end
end
if values_from_section.nil? && global_defaults_initialized?
values_from_section = ValuesFromEnvironmentConf.new(name)
end
values_from_section
end
end.compact
end
# Read the file in.
# @api private
def read_file(file)
return Puppet::FileSystem.read(file)
end
# Private method for internal test use only; allows to do a comprehensive clear of all settings between tests.
#
# @return nil
def clear_everything_for_tests()
unsafe_clear(true, true)
@configuration_file = nil
@global_defaults_initialized = false
@app_defaults_initialized = false
end
private :clear_everything_for_tests
def explicit_config_file?
# Figure out if the user has provided an explicit configuration file. If
# so, return the path to the file, if not return nil.
#
# The easiest way to determine whether an explicit one has been specified
# is to simply attempt to evaluate the value of ":config". This will
# obviously be successful if they've passed an explicit value for :config,
# but it will also result in successful interpolation if they've only
# passed an explicit value for :confdir.
#
# If they've specified neither, then the interpolation will fail and we'll
# get an exception.
#
begin
return true if self[:config]
rescue InterpolationError
# This means we failed to interpolate, which means that they didn't
# explicitly specify either :config or :confdir... so we'll fall out to
# the default value.
return false
end
end
private :explicit_config_file?
# Lookup configuration setting value through a chain of different value sources.
#
# @api public
class ChainedValues
ENVIRONMENT_SETTING = "environment".freeze
+ ENVIRONMENT_INTERPOLATION_ALLOWED = ['config_version'].freeze
# @see Puppet::Settings.values
# @api private
def initialize(mode, environment, value_sets, defaults)
@mode = mode
@environment = environment
@value_sets = value_sets
@defaults = defaults
end
# Lookup the uninterpolated value.
#
# @param name [Symbol] The configuration setting name to look up
# @return [Object] The configuration setting value or nil if the setting is not known
# @api public
def lookup(name)
set = @value_sets.find do |set|
set.include?(name)
end
if set
value = set.lookup(name)
if !value.nil?
return value
end
end
@defaults[name].default
end
# Lookup the interpolated value. All instances of `$name` in the value will
# be replaced by performing a lookup of `name` and substituting the text
# for `$name` in the original value. This interpolation is only performed
# if the looked up value is a String.
#
# @param name [Symbol] The configuration setting name to look up
# @return [Object] The configuration setting value or nil if the setting is not known
# @api public
def interpolate(name)
setting = @defaults[name]
if setting
val = lookup(name)
# if we interpolate code, all hell breaks loose.
if name == :code
val
else
# Convert it if necessary
begin
- val = convert(val)
+ val = convert(val, name)
rescue InterpolationError => err
# This happens because we don't have access to the param name when the
# exception is originally raised, but we want it in the message
raise InterpolationError, "Error converting value for param '#{name}': #{err}", err.backtrace
end
setting.munge(val)
end
else
nil
end
end
private
- def convert(value)
+ def convert(value, setting_name)
case value
when nil
nil
when String
- value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
+ failed_environment_interpolation = false
+ interpolated_value = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |expression|
varname = $2 || $1
- if varname == ENVIRONMENT_SETTING && @environment
- @environment
- elsif varname == "run_mode"
- @mode
- elsif !(pval = interpolate(varname.to_sym)).nil?
- pval
+ interpolated_expression =
+ if varname != ENVIRONMENT_SETTING || ok_to_interpolate_environment(setting_name)
+ if varname == ENVIRONMENT_SETTING && @environment
+ @environment
+ elsif varname == "run_mode"
+ @mode
+ elsif !(pval = interpolate(varname.to_sym)).nil?
+ pval
+ else
+ raise InterpolationError, "Could not find value for #{expression}"
+ end
else
- raise InterpolationError, "Could not find value for #{value}"
+ failed_environment_interpolation = true
+ expression
end
+ interpolated_expression
end
+ if failed_environment_interpolation
+ Puppet.warning("You cannot interpolate $environment within '#{setting_name}' when using directory environments. Its value will remain #{interpolated_value}.")
+ end
+ interpolated_value
else
value
end
end
+
+ def ok_to_interpolate_environment(setting_name)
+ return true if Puppet.settings.value(:environmentpath, nil, true).empty?
+
+ ENVIRONMENT_INTERPOLATION_ALLOWED.include?(setting_name.to_s)
+ end
end
class Values
extend Forwardable
def initialize(name, defaults)
@name = name
@values = {}
@defaults = defaults
end
def_delegator :@values, :include?
def_delegator :@values, :[], :lookup
def set(name, value)
default = @defaults[name]
if !default
raise ArgumentError,
"Attempt to assign a value to unknown setting #{name.inspect}"
end
if default.has_hook?
default.handle(value)
end
@values[name] = value
end
end
class ValuesFromSection
def initialize(name, section)
@name = name
@section = section
end
def include?(name)
!@section.setting(name).nil?
end
def lookup(name)
setting = @section.setting(name)
if setting
setting.value
end
end
end
# @api private
class ValuesFromEnvironmentConf
def initialize(environment_name)
@environment_name = environment_name
end
def include?(name)
if Puppet::Settings::EnvironmentConf::VALID_SETTINGS.include?(name) && conf
return true
end
false
end
def lookup(name)
return nil unless Puppet::Settings::EnvironmentConf::VALID_SETTINGS.include?(name)
conf.send(name) if conf
end
def conf
@conf ||= if environments = Puppet.lookup(:environments)
environments.get_conf(@environment_name)
end
end
end
end
diff --git a/lib/puppet/ssl/validator/default_validator.rb b/lib/puppet/ssl/validator/default_validator.rb
index 1f31499e2..674b3c224 100644
--- a/lib/puppet/ssl/validator/default_validator.rb
+++ b/lib/puppet/ssl/validator/default_validator.rb
@@ -1,154 +1,175 @@
require 'openssl'
require 'puppet/ssl'
# Perform peer certificate verification against the known CA.
# If there is no CA information known, then no verification is performed
#
# @api private
#
class Puppet::SSL::Validator::DefaultValidator #< class Puppet::SSL::Validator
attr_reader :peer_certs
attr_reader :verify_errors
attr_reader :ssl_configuration
+ FIVE_MINUTES_AS_SECONDS = 5 * 60
+
# Creates a new DefaultValidator, optionally with an SSL Configuration and SSL Host.
#
# @param ssl_configuration [Puppet::SSL::Configuration] (a default configuration) ssl_configuration the SSL configuration to use
# @param ssl_host [Puppet::SSL::Host] (Puppet::SSL::Host.localhost) the SSL host to use
#
# @api private
#
def initialize(
ssl_configuration = Puppet::SSL::Configuration.new(
Puppet[:localcacert], {
:ca_chain_file => Puppet[:ssl_client_ca_chain],
:ca_auth_file => Puppet[:ssl_client_ca_auth]
}),
ssl_host = Puppet::SSL::Host.localhost)
reset!
@ssl_configuration = ssl_configuration
@ssl_host = ssl_host
end
# Resets this validator to its initial validation state. The ssl configuration is not changed.
#
# @api private
#
def reset!
@peer_certs = []
@verify_errors = []
end
# Performs verification of the SSL connection and collection of the
# certificates for use in constructing the error message if the verification
# failed. This callback will be executed once for each certificate in a
# chain being verified.
#
# From the [OpenSSL
# documentation](http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html):
# The `verify_callback` function is used to control the behaviour when the
# SSL_VERIFY_PEER flag is set. It must be supplied by the application and
# receives two arguments: preverify_ok indicates, whether the verification of
# the certificate in question was passed (preverify_ok=1) or not
- # (preverify_ok=0). x509_ctx is a pointer to the complete context used for
+ # (preverify_ok=0). x509_store_ctx is a pointer to the complete context used for
# the certificate chain verification.
#
# See {Puppet::Network::HTTP::Connection} for more information and where this
# class is intended to be used.
#
# @param [Boolean] preverify_ok indicates whether the verification of the
# certificate in question was passed (preverify_ok=true)
- # @param [OpenSSL::SSL::SSLContext] ssl_context holds the SSLContext for the
- # chain being verified.
+ # @param [OpenSSL::X509::StoreContext] store_context holds the X509 store context
+ # for the chain being verified.
#
# @return [Boolean] false if the peer is invalid, true otherwise.
#
# @api private
#
- def call(preverify_ok, ssl_context)
- # We must make a copy since the scope of the ssl_context will be lost
+ def call(preverify_ok, store_context)
+ # We must make a copy since the scope of the store_context will be lost
# across invocations of this method.
- current_cert = ssl_context.current_cert
- @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert)
-
if preverify_ok
+ current_cert = store_context.current_cert
+ @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert)
+
# If we've copied all of the certs in the chain out of the SSL library
- if @peer_certs.length == ssl_context.chain.length
+ if @peer_certs.length == store_context.chain.length
# (#20027) The peer cert must be issued by a specific authority
preverify_ok = valid_peer?
end
else
- if ssl_context.error_string
- @verify_errors << "#{ssl_context.error_string} for #{current_cert.subject}"
+ error = store_context.error || 0
+ error_string = store_context.error_string || "OpenSSL error #{error}"
+
+ case error
+ when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID
+ # current_crl can be nil
+ # https://github.com/ruby/ruby/blob/ruby_1_9_3/ext/openssl/ossl_x509store.c#L501-L510
+ crl = store_context.current_crl
+ if crl
+ if crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS
+ Puppet.debug("Ignoring CRL not yet valid, current time #{Time.now.utc}, CRL last updated #{crl.last_update.utc}")
+ preverify_ok = true
+ else
+ @verify_errors << "#{error_string} for #{crl.issuer}"
+ end
+ else
+ @verify_errors << error_string
+ end
+ else
+ current_cert = store_context.current_cert
+ @verify_errors << "#{error_string} for #{current_cert.subject}"
end
end
preverify_ok
rescue => ex
@verify_errors << ex.message
false
end
# Registers the instance's call method with the connection.
#
# @param [Net::HTTP] connection The connection to validate
#
# @return [void]
#
# @api private
#
def setup_connection(connection)
if ssl_certificates_are_present?
connection.cert_store = @ssl_host.ssl_store
connection.ca_file = @ssl_configuration.ca_auth_file
connection.cert = @ssl_host.certificate.content
connection.key = @ssl_host.key.content
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
connection.verify_callback = self
else
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
# Validates the peer certificates against the authorized certificates.
#
# @api private
#
def valid_peer?
descending_cert_chain = @peer_certs.reverse.map {|c| c.content }
authz_ca_certs = ssl_configuration.ca_auth_certificates
if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs)
msg = "The server presented a SSL certificate chain which does not include a " <<
"CA listed in the ssl_client_ca_auth file. "
msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " <<
"Peer Chain: #{descending_cert_chain.collect {|c| c.subject}.join(' => ')}"
@verify_errors << msg
false
else
true
end
end
# Checks if the set of peer_certs contains at least one certificate issued
# by a certificate listed in authz_certs
#
# @return [Boolean]
#
# @api private
#
def has_authz_peer_cert(peer_certs, authz_certs)
peer_certs.any? do |peer_cert|
authz_certs.any? do |authz_cert|
peer_cert.verify(authz_cert.public_key)
end
end
end
# @api private
#
def ssl_certificates_are_present?
Puppet::FileSystem.exist?(Puppet[:hostcert]) && Puppet::FileSystem.exist?(@ssl_configuration.ca_auth_file)
end
end
diff --git a/spec/fixtures/unit/provider/service/openbsd/rcctl_status b/spec/fixtures/unit/provider/service/openbsd/rcctl_status
new file mode 100644
index 000000000..f58ce12e3
--- /dev/null
+++ b/spec/fixtures/unit/provider/service/openbsd/rcctl_status
@@ -0,0 +1,6 @@
+accounting=NO
+pf=YES
+postgresql_flags=-l /var/postgresql/logfile
+tftpd_flags=/tftpboot
+wsmoused_flags=NO
+xdm_flags=
diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb
index 734785230..7d8ea3150 100755
--- a/spec/integration/defaults_spec.rb
+++ b/spec/integration/defaults_spec.rb
@@ -1,341 +1,335 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/defaults'
require 'puppet/rails'
describe "Puppet defaults" do
describe "when default_manifest is set" do
it "returns ./manifests by default" do
expect(Puppet[:default_manifest]).to eq('./manifests')
end
-
- it "errors when $environment is part of the value" do
- expect {
- Puppet[:default_manifest] = '/$environment/manifest.pp'
- }.to raise_error Puppet::Settings::ValidationError, /cannot interpolate.*\$environment/
- end
end
describe "when disable_per_environment_manifest is set" do
it "returns false by default" do
expect(Puppet[:disable_per_environment_manifest]).to eq(false)
end
it "errors when set to true and default_manifest is not an absolute path" do
expect {
Puppet[:default_manifest] = './some/relative/manifest.pp'
Puppet[:disable_per_environment_manifest] = true
}.to raise_error Puppet::Settings::ValidationError, /'default_manifest' setting must be.*absolute/
end
end
describe "when setting the :factpath" do
it "should add the :factpath to Facter's search paths" do
Facter.expects(:search).with("/my/fact/path")
Puppet.settings[:factpath] = "/my/fact/path"
end
end
describe "when setting the :certname" do
it "should fail if the certname is not downcased" do
expect { Puppet.settings[:certname] = "Host.Domain.Com" }.to raise_error(ArgumentError)
end
end
describe "when setting :node_name_value" do
it "should default to the value of :certname" do
Puppet.settings[:certname] = 'blargle'
Puppet.settings[:node_name_value].should == 'blargle'
end
end
describe "when setting the :node_name_fact" do
it "should fail when also setting :node_name_value" do
lambda do
Puppet.settings[:node_name_value] = "some value"
Puppet.settings[:node_name_fact] = "some_fact"
end.should raise_error("Cannot specify both the node_name_value and node_name_fact settings")
end
it "should not fail when using the default for :node_name_value" do
lambda do
Puppet.settings[:node_name_fact] = "some_fact"
end.should_not raise_error
end
end
describe "when :certdnsnames is set" do
it "should not fail" do
expect { Puppet[:certdnsnames] = 'fred:wilma' }.to_not raise_error
end
it "should warn the value is ignored" do
Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ }
Puppet[:certdnsnames] = 'fred:wilma'
end
end
describe "when setting the :catalog_format" do
it "should log a deprecation notice" do
Puppet.expects(:deprecation_warning)
Puppet.settings[:catalog_format] = 'marshal'
end
it "should copy the value to :preferred_serialization_format" do
Puppet.settings[:catalog_format] = 'marshal'
Puppet.settings[:preferred_serialization_format].should == 'marshal'
end
end
it "should have a clientyamldir setting" do
Puppet.settings[:clientyamldir].should_not be_nil
end
it "should have different values for the yamldir and clientyamldir" do
Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir]
end
it "should have a client_datadir setting" do
Puppet.settings[:client_datadir].should_not be_nil
end
it "should have different values for the server_datadir and client_datadir" do
Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir]
end
# See #1232
it "should not specify a user or group for the clientyamldir" do
Puppet.settings.setting(:clientyamldir).owner.should be_nil
Puppet.settings.setting(:clientyamldir).group.should be_nil
end
it "should use the service user and group for the yamldir" do
Puppet.settings.stubs(:service_user_available?).returns true
Puppet.settings.stubs(:service_group_available?).returns true
Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user]
Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group]
end
it "should specify that the host private key should be owned by the service user" do
Puppet.settings.stubs(:service_user_available?).returns true
Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user]
end
it "should specify that the host certificate should be owned by the service user" do
Puppet.settings.stubs(:service_user_available?).returns true
Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user]
end
[:modulepath, :factpath].each do |setting|
it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do
Puppet.settings.setting(setting).should be_instance_of(Puppet::Settings::PathSetting)
end
end
describe "on a Unix-like platform it", :as_platform => :posix do
it "should add /usr/sbin and /sbin to the path if they're not there" do
Puppet::Util.withenv("PATH" => "/usr/bin#{File::PATH_SEPARATOR}/usr/local/bin") do
Puppet.settings[:path] = "none" # this causes it to ignore the setting
ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin")
ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin")
end
end
end
describe "on a Windows-like platform it", :as_platform => :windows do
it "should not add anything" do
path = "c:\\windows\\system32#{File::PATH_SEPARATOR}c:\\windows"
Puppet::Util.withenv("PATH" => path) do
Puppet.settings[:path] = "none" # this causes it to ignore the setting
ENV["PATH"].should == path
end
end
end
it "should default to pson for the preferred serialization format" do
Puppet.settings.value(:preferred_serialization_format).should == "pson"
end
describe "when enabling storeconfigs" do
before do
Puppet::Resource::Catalog.indirection.stubs(:cache_class=)
Puppet::Node::Facts.indirection.stubs(:cache_class=)
Puppet::Node.indirection.stubs(:cache_class=)
Puppet.features.stubs(:rails?).returns true
end
it "should set the Catalog cache class to :store_configs" do
Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs)
Puppet.settings[:storeconfigs] = true
end
it "should not set the Catalog cache class to :store_configs if asynchronous storeconfigs is enabled" do
Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs).never
Puppet.settings[:async_storeconfigs] = true
Puppet.settings[:storeconfigs] = true
end
it "should set the Facts cache class to :store_configs" do
Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs)
Puppet.settings[:storeconfigs] = true
end
it "does not change the Node cache" do
Puppet::Node.indirection.expects(:cache_class=).never
Puppet.settings[:storeconfigs] = true
end
end
describe "when enabling asynchronous storeconfigs" do
before do
Puppet::Resource::Catalog.indirection.stubs(:cache_class=)
Puppet::Node::Facts.indirection.stubs(:cache_class=)
Puppet::Node.indirection.stubs(:cache_class=)
Puppet.features.stubs(:rails?).returns true
end
it "should set storeconfigs to true" do
Puppet.settings[:async_storeconfigs] = true
Puppet.settings[:storeconfigs].should be_true
end
it "should set the Catalog cache class to :queue" do
Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue)
Puppet.settings[:async_storeconfigs] = true
end
it "should set the Facts cache class to :store_configs" do
Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs)
Puppet.settings[:storeconfigs] = true
end
it "does not change the Node cache" do
Puppet::Node.indirection.expects(:cache_class=).never
Puppet.settings[:storeconfigs] = true
end
end
describe "when enabling thin storeconfigs" do
before do
Puppet::Resource::Catalog.indirection.stubs(:cache_class=)
Puppet::Node::Facts.indirection.stubs(:cache_class=)
Puppet::Node.indirection.stubs(:cache_class=)
Puppet.features.stubs(:rails?).returns true
end
it "should set storeconfigs to true" do
Puppet.settings[:thin_storeconfigs] = true
Puppet.settings[:storeconfigs].should be_true
end
end
it "should have a setting for determining the configuration version and should default to an empty string" do
Puppet.settings[:config_version].should == ""
end
describe "when enabling reports" do
it "should use the default server value when report server is unspecified" do
Puppet.settings[:server] = "server"
Puppet.settings[:report_server].should == "server"
end
it "should use the default masterport value when report port is unspecified" do
Puppet.settings[:masterport] = "1234"
Puppet.settings[:report_port].should == "1234"
end
it "should use report_port when set" do
Puppet.settings[:masterport] = "1234"
Puppet.settings[:report_port] = "5678"
Puppet.settings[:report_port].should == "5678"
end
end
it "should have a :caname setting that defaults to the cert name" do
Puppet.settings[:certname] = "foo"
Puppet.settings[:ca_name].should == "Puppet CA: foo"
end
it "should have a 'prerun_command' that defaults to the empty string" do
Puppet.settings[:prerun_command].should == ""
end
it "should have a 'postrun_command' that defaults to the empty string" do
Puppet.settings[:postrun_command].should == ""
end
it "should have a 'certificate_revocation' setting that defaults to true" do
Puppet.settings[:certificate_revocation].should be_true
end
it "should have an http_compression setting that defaults to false" do
Puppet.settings[:http_compression].should be_false
end
describe "reportdir" do
subject { Puppet.settings[:reportdir] }
it { should == "#{Puppet[:vardir]}/reports" }
end
describe "reporturl" do
subject { Puppet.settings[:reporturl] }
it { should == "http://localhost:3000/reports/upload" }
end
describe "when configuring color" do
subject { Puppet.settings[:color] }
it { should == "ansi" }
end
describe "daemonize" do
it "should default to true", :unless => Puppet.features.microsoft_windows? do
Puppet.settings[:daemonize].should == true
end
describe "on Windows", :if => Puppet.features.microsoft_windows? do
it "should default to false" do
Puppet.settings[:daemonize].should == false
end
it "should raise an error if set to true" do
expect { Puppet.settings[:daemonize] = true }.to raise_error(/Cannot daemonize on Windows/)
end
end
end
describe "diff" do
it "should default to 'diff' on POSIX", :unless => Puppet.features.microsoft_windows? do
Puppet.settings[:diff].should == 'diff'
end
it "should default to '' on Windows", :if => Puppet.features.microsoft_windows? do
Puppet.settings[:diff].should == ''
end
end
describe "when configuring hiera" do
it "should have a hiera_config setting" do
Puppet.settings[:hiera_config].should_not be_nil
end
end
describe "when configuring the data_binding terminus" do
it "should have a data_binding_terminus setting" do
Puppet.settings[:data_binding_terminus].should_not be_nil
end
it "should be set to hiera by default" do
Puppet.settings[:data_binding_terminus].should == :hiera
end
end
describe "agent_catalog_run_lockfile" do
it "(#2888) is not a file setting so it is absent from the Settings catalog" do
Puppet.settings.setting(:agent_catalog_run_lockfile).should_not be_a_kind_of Puppet::Settings::FileSetting
Puppet.settings.setting(:agent_catalog_run_lockfile).should be_a Puppet::Settings::StringSetting
end
end
end
diff --git a/spec/integration/environments/default_manifest_spec.rb b/spec/integration/environments/default_manifest_spec.rb
index 6d4564037..543afc087 100644
--- a/spec/integration/environments/default_manifest_spec.rb
+++ b/spec/integration/environments/default_manifest_spec.rb
@@ -1,274 +1,263 @@
require 'spec_helper'
module EnvironmentsDefaultManifestsSpec
describe "default manifests" do
- FS = Puppet::FileSystem
shared_examples_for "puppet with default_manifest settings" do
let(:confdir) { Puppet[:confdir] }
let(:environmentpath) { File.expand_path("envdir", confdir) }
context "relative default" do
let(:testingdir) { File.join(environmentpath, "testing") }
before(:each) do
FileUtils.mkdir_p(testingdir)
end
it "reads manifest from ./manifest of a basic directory environment" do
manifestsdir = File.join(testingdir, "manifests")
FileUtils.mkdir_p(manifestsdir)
File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromRelativeDefault': }")
end
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts("environmentpath=#{environmentpath}")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromRelativeDefault]')
)
end
end
context "set absolute" do
let(:testingdir) { File.join(environmentpath, "testing") }
before(:each) do
FileUtils.mkdir_p(testingdir)
end
it "reads manifest from an absolute default_manifest" do
manifestsdir = File.expand_path("manifests", confdir)
FileUtils.mkdir_p(manifestsdir)
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
environmentpath=#{environmentpath}
default_manifest=#{manifestsdir}
EOF
end
File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
)
end
it "reads manifest from directory environment manifest when environment.conf manifest set" do
default_manifestsdir = File.expand_path("manifests", confdir)
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
environmentpath=#{environmentpath}
default_manifest=#{default_manifestsdir}
EOF
end
manifestsdir = File.join(testingdir, "special_manifests")
FileUtils.mkdir_p(manifestsdir)
File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromEnvironmentConfManifest': }")
end
File.open(File.join(testingdir, "environment.conf"), "w") do |f|
f.puts("manifest=./special_manifests")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromEnvironmentConfManifest]')
)
expect(Puppet[:default_manifest]).to eq(default_manifestsdir)
end
it "ignores manifests in the local ./manifests if default_manifest specifies another directory" do
default_manifestsdir = File.expand_path("manifests", confdir)
FileUtils.mkdir_p(default_manifestsdir)
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
environmentpath=#{environmentpath}
default_manifest=#{default_manifestsdir}
EOF
end
File.open(File.join(default_manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
end
implicit_manifestsdir = File.join(testingdir, "manifests")
FileUtils.mkdir_p(implicit_manifestsdir)
File.open(File.join(implicit_manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromImplicitRelativeEnvironmentManifestDirectory': }")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
)
end
- it "raises an exception if default_manifest has $environment in it" do
- File.open(File.join(confdir, "puppet.conf"), "w") do |f|
- f.puts(<<-EOF)
- environmentpath=#{environmentpath}
- default_manifest=/foo/$environment
- EOF
- end
-
- expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /cannot interpolate.*\$environment.*in.*default_manifest/)
- end
end
context "with disable_per_environment_manifest true" do
let(:manifestsdir) { File.expand_path("manifests", confdir) }
let(:testingdir) { File.join(environmentpath, "testing") }
before(:each) do
FileUtils.mkdir_p(testingdir)
end
before(:each) do
FileUtils.mkdir_p(manifestsdir)
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
environmentpath=#{environmentpath}
default_manifest=#{manifestsdir}
disable_per_environment_manifest=true
EOF
end
File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
end
end
it "reads manifest from the default manifest setting" do
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
)
end
it "refuses to compile if environment.conf specifies a different manifest" do
File.open(File.join(testingdir, "environment.conf"), "w") do |f|
f.puts("manifest=./special_manifests")
end
expect { a_catalog_compiled_for_environment('testing') }.to(
raise_error(Puppet::Error, /disable_per_environment_manifest.*environment.conf.*manifest.*conflict/)
)
end
it "reads manifest from default_manifest setting when environment.conf has manifest set if setting equals default_manifest setting" do
File.open(File.join(testingdir, "environment.conf"), "w") do |f|
f.puts("manifest=#{manifestsdir}")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromAbsoluteDefaultManifest]')
)
end
it "logs errors if environment.conf specifies a different manifest" do
File.open(File.join(testingdir, "environment.conf"), "w") do |f|
f.puts("manifest=./special_manifests")
end
Puppet.initialize_settings
expect(Puppet[:environmentpath]).to eq(environmentpath)
environment = Puppet.lookup(:environments).get('testing')
expect(environment.manifest).to eq(manifestsdir)
expect(@logs.first.to_s).to match(%r{disable_per_environment_manifest.*is true, but.*environment.*at #{testingdir}.*has.*environment.conf.*manifest.*#{testingdir}/special_manifests})
end
it "raises an error if default_manifest is not absolute" do
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
environmentpath=#{environmentpath}
default_manifest=./relative
disable_per_environment_manifest=true
EOF
end
expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /default_manifest.*must be.*absolute.*when.*disable_per_environment_manifest.*true/)
end
end
context "in legacy environments" do
let(:environmentpath) { '' }
let(:manifestsdir) { File.expand_path("default_manifests", confdir) }
let(:legacy_manifestsdir) { File.expand_path('manifests', confdir) }
before(:each) do
FileUtils.mkdir_p(manifestsdir)
File.open(File.join(confdir, "puppet.conf"), "w") do |f|
f.puts(<<-EOF)
default_manifest=#{manifestsdir}
disable_per_environment_manifest=true
manifest=#{legacy_manifestsdir}
EOF
end
File.open(File.join(manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }")
end
end
it "has no effect on compilation" do
FileUtils.mkdir_p(legacy_manifestsdir)
File.open(File.join(legacy_manifestsdir, "site.pp"), "w") do |f|
f.puts("notify { 'ManifestFromLegacy': }")
end
expect(a_catalog_compiled_for_environment('testing')).to(
include_resource('Notify[ManifestFromLegacy]')
)
end
end
end
describe 'using future parser' do
before :each do
Puppet[:parser] = 'future'
end
it_behaves_like 'puppet with default_manifest settings'
end
describe 'using current parser' do
before :each do
Puppet[:parser] = 'current'
end
it_behaves_like 'puppet with default_manifest settings'
end
RSpec::Matchers.define :include_resource do |expected|
match do |actual|
actual.resources.map(&:ref).include?(expected)
end
def failure_message_for_should
"expected #{@actual.resources.map(&:ref)} to include #{expected}"
end
def failure_message_for_should_not
"expected #{@actual.resources.map(&:ref)} not to include #{expected}"
end
end
def a_catalog_compiled_for_environment(envname)
Puppet.initialize_settings
expect(Puppet[:environmentpath]).to eq(environmentpath)
node = Puppet::Node.new('testnode', :environment => 'testing')
expect(node.environment).to eq(Puppet.lookup(:environments).get('testing'))
Puppet::Parser::Compiler.compile(node)
end
end
end
diff --git a/spec/integration/environments/settings_interpolation_spec.rb b/spec/integration/environments/settings_interpolation_spec.rb
new file mode 100644
index 000000000..bcdc136c7
--- /dev/null
+++ b/spec/integration/environments/settings_interpolation_spec.rb
@@ -0,0 +1,165 @@
+require 'pp'
+require 'spec_helper'
+
+module SettingsInterpolationSpec
+describe "interpolating $environment" do
+ let(:confdir) { Puppet[:confdir] }
+ let(:cmdline_args) { ['--confdir', confdir, '--vardir', Puppet[:vardir], '--hiera_config', Puppet[:hiera_config]] }
+
+ before(:each) do
+ FileUtils.mkdir_p(confdir)
+ end
+
+ shared_examples_for "a setting that does not interpolate $environment" do
+
+ before(:each) do
+ set_puppet_conf(confdir, <<-EOF)
+ environmentpath=$confdir/environments
+ #{setting}=#{value}
+ EOF
+ end
+
+ it "does not interpolate $environment" do
+ Puppet.initialize_settings(cmdline_args)
+ expect(Puppet[:environmentpath]).to eq("#{confdir}/environments")
+ expect(Puppet[setting.intern]).to eq(expected)
+ end
+
+ it "displays the interpolated value in the warning" do
+ Puppet.initialize_settings(cmdline_args)
+ Puppet[setting.intern]
+ expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'.*Its value will remain #{Regexp.escape(expected)}/)
+ end
+ end
+
+ context "when environmentpath is set" do
+
+ describe "config_version" do
+ it "interpolates $environment" do
+ envname = 'testing'
+ setting = 'config_version'
+ value = '/some/script $environment'
+ expected = "#{File.expand_path('/some/script')} testing"
+
+ set_puppet_conf(confdir, <<-EOF)
+ environmentpath=$confdir/environments
+ environment=#{envname}
+ EOF
+
+ set_environment_conf("#{confdir}/environments", envname, <<-EOF)
+ #{setting}=#{value}
+ EOF
+
+ Puppet.initialize_settings(cmdline_args)
+ expect(Puppet[:environmentpath]).to eq("#{confdir}/environments")
+ environment = Puppet.lookup(:environments).get(envname)
+ expect(environment.config_version).to eq(expected)
+ expect(@logs).to be_empty
+ end
+ end
+
+ describe "basemodulepath" do
+ let(:setting) { "basemodulepath" }
+ let(:value) { "$confdir/environments/$environment/modules:$confdir/environments/$environment/other_modules" }
+ let(:expected) { "#{confdir}/environments/$environment/modules:#{confdir}/environments/$environment/other_modules" }
+
+ it_behaves_like "a setting that does not interpolate $environment"
+
+ it "logs a single warning for multiple instaces of $environment in the setting" do
+ set_puppet_conf(confdir, <<-EOF)
+ environmentpath=$confdir/environments
+ #{setting}=#{value}
+ EOF
+
+ Puppet.initialize_settings(cmdline_args)
+ expect(@logs.map(&:to_s).grep(/cannot interpolate \$environment within '#{setting}'/).count).to eq(1)
+ end
+ end
+
+ describe "environment" do
+ let(:setting) { "environment" }
+ let(:value) { "whatareyouthinking$environment" }
+ let(:expected) { value }
+
+ it_behaves_like "a setting that does not interpolate $environment"
+ end
+
+ describe "the default_manifest" do
+ let(:setting) { "default_manifest" }
+ let(:value) { "$confdir/manifests/$environment" }
+ let(:expected) { "#{confdir}/manifests/$environment" }
+
+ it_behaves_like "a setting that does not interpolate $environment"
+ end
+
+ it "does not interpolate $environment and logs a warning when interpolating environmentpath" do
+ setting = 'environmentpath'
+ value = "$confdir/environments/$environment"
+ expected = "#{confdir}/environments/$environment"
+
+ set_puppet_conf(confdir, <<-EOF)
+ #{setting}=#{value}
+ EOF
+
+ Puppet.initialize_settings(cmdline_args)
+ expect(Puppet[setting.intern]).to eq(expected)
+ expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'/)
+ end
+ end
+
+ def assert_does_interpolate_environment(setting, value, expected_interpolation)
+ set_puppet_conf(confdir, <<-EOF)
+ #{setting}=#{value}
+ EOF
+
+ Puppet.initialize_settings(cmdline_args)
+ expect(Puppet[:environmentpath]).to be_empty
+ expect(Puppet[setting.intern]).to eq(expected_interpolation)
+ expect(@logs).to_not have_matching_log(/cannot interpolate \$environment within '#{setting}'/)
+ end
+
+ context "when environmentpath is not set" do
+ it "does interpolate $environment in config_version" do
+ value = "/some/script $environment"
+ expect = "/some/script production"
+ assert_does_interpolate_environment("config_version", value, expect)
+ end
+
+ it "does interpolate $environment in basemodulepath" do
+ value = "$confdir/environments/$environment/modules:$confdir/environments/$environment/other_modules"
+ expected = "#{confdir}/environments/production/modules:#{confdir}/environments/production/other_modules"
+ assert_does_interpolate_environment("basemodulepath", value, expected)
+ end
+
+ it "does interpolate $environment in default_manifest, which is fine, because this setting isn't used" do
+ value = "$confdir/manifests/$environment"
+ expected = "#{confdir}/manifests/production"
+
+ assert_does_interpolate_environment("default_manifest", value, expected)
+ end
+
+ it "raises something" do
+ value = expected = "whatareyouthinking$environment"
+ expect {
+ assert_does_interpolate_environment("environment", value, expected)
+ }.to raise_error(SystemStackError, /stack level too deep/)
+ end
+ end
+
+ def set_puppet_conf(confdir, settings)
+ write_file(File.join(confdir, "puppet.conf"), settings)
+ end
+
+ def set_environment_conf(environmentpath, environment, settings)
+ envdir = File.join(environmentpath, environment)
+ FileUtils.mkdir_p(envdir)
+ write_file(File.join(envdir, 'environment.conf'), settings)
+ end
+
+ def write_file(file, contents)
+ File.open(file, "w") do |f|
+ f.puts(contents)
+ end
+ end
+end
+end
diff --git a/spec/integration/provider/service/windows_spec.rb b/spec/integration/provider/service/windows_spec.rb
new file mode 100644
index 000000000..79d18144d
--- /dev/null
+++ b/spec/integration/provider/service/windows_spec.rb
@@ -0,0 +1,48 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+describe Puppet::Type.type(:service).provider(:windows), '(integration)',
+ :if => Puppet.features.microsoft_windows? do
+
+ require 'puppet/util/windows'
+
+ before :each do
+ Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class
+ end
+
+ context 'should fail querying services that do not exist' do
+ let(:service) do
+ Puppet::Type.type(:service).new(:name => 'foobarservice1234')
+ end
+
+ it "with a Puppet::Error when querying enabled?" do
+ expect { service.provider.enabled? }.to raise_error(Puppet::Error)
+ end
+
+ it "with a Puppet::Error when querying status" do
+ expect { service.provider.status }.to raise_error(Puppet::Error)
+ end
+ end
+
+ context 'should return valid values when querying a service that does exist' do
+ let(:service) do
+ Puppet::Type.type(:service).new(:name => 'lmhosts')
+ end
+
+ it "with a valid boolean when asked if enabled" do
+ expect([:true, :false]).to include(service.provider.enabled?)
+ end
+
+ it "with a valid status when asked about status" do
+ expect([
+ :running,
+ :'continue pending',
+ :'pause pending',
+ :paused,
+ :running,
+ :'start pending',
+ :'stop pending',
+ :stopped]).to include(service.provider.status)
+ end
+ end
+end
diff --git a/spec/integration/util/windows/security_spec.rb b/spec/integration/util/windows/security_spec.rb
index 7f7aa7cb6..70d4c4fa6 100755
--- a/spec/integration/util/windows/security_spec.rb
+++ b/spec/integration/util/windows/security_spec.rb
@@ -1,864 +1,864 @@
#!/usr/bin/env ruby
require 'spec_helper'
if Puppet.features.microsoft_windows?
class WindowsSecurityTester
require 'puppet/util/windows/security'
include Puppet::Util::Windows::Security
end
end
describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_windows? do
include PuppetSpec::Files
before :all do
@sids = {
:current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name),
:system => Win32::Security::SID::LocalSystem,
:admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"),
:administrators => Win32::Security::SID::BuiltinAdministrators,
:guest => Puppet::Util::Windows::SID.name_to_sid("Guest"),
:users => Win32::Security::SID::BuiltinUsers,
:power_users => Win32::Security::SID::PowerUsers,
:none => Win32::Security::SID::Nobody,
:everyone => Win32::Security::SID::Everyone
}
# The TCP/IP NetBIOS Helper service (aka 'lmhosts') has ended up
# disabled on some VMs for reasons we couldn't track down. This
# condition causes tests which rely on resolving UNC style paths
# (like \\localhost) to fail with unhelpful error messages.
# Put a check for this upfront to aid debug should this strike again.
service = Puppet::Type.type(:service).new(:name => 'lmhosts')
expect(service.provider.status).to eq(:running), 'lmhosts service is not running'
end
let (:sids) { @sids }
let (:winsec) { WindowsSecurityTester.new }
let (:klass) { Puppet::Util::Windows::File }
def set_group_depending_on_current_user(path)
if sids[:current_user] == sids[:system]
# if the current user is SYSTEM, by setting the group to
# guest, SYSTEM is automagically given full control, so instead
# override that behavior with SYSTEM as group and a specific mode
winsec.set_group(sids[:system], path)
mode = winsec.get_mode(path)
winsec.set_mode(mode & ~WindowsSecurityTester::S_IRWXG, path)
else
winsec.set_group(sids[:guest], path)
end
end
def grant_everyone_full_access(path)
sd = winsec.get_security_descriptor(path)
everyone = 'S-1-1-0'
inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd.dacl.allow(everyone, klass::FILE_ALL_ACCESS, inherit)
winsec.set_security_descriptor(path, sd)
end
shared_examples_for "only child owner" do
it "should allow child owner" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0700, parent)
check_delete(path)
end
it "should deny parent owner" do
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
lambda { check_delete(path) }.should raise_error(Errno::EACCES)
end
it "should deny group" do
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
lambda { check_delete(path) }.should raise_error(Errno::EACCES)
end
it "should deny other" do
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
lambda { check_delete(path) }.should raise_error(Errno::EACCES)
end
end
shared_examples_for "a securable object" do
describe "on a volume that doesn't support ACLs" do
[:owner, :group, :mode].each do |p|
it "should return nil #{p}" do
winsec.stubs(:supports_acl?).returns false
winsec.send("get_#{p}", path).should be_nil
end
end
end
describe "on a volume that supports ACLs" do
describe "for a normal user" do
before :each do
Puppet.features.stubs(:root?).returns(false)
end
after :each do
winsec.set_mode(WindowsSecurityTester::S_IRWXU, parent)
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) if Puppet::FileSystem.exist?(path)
end
describe "#supports_acl?" do
%w[c:/ c:\\ c:/windows/system32 \\\\localhost\\C$ \\\\127.0.0.1\\C$\\foo].each do |path|
it "should accept #{path}" do
winsec.should be_supports_acl(path)
end
end
it "should raise an exception if it cannot get volume information" do
expect {
winsec.supports_acl?('foobar')
}.to raise_error(Puppet::Error, /Failed to get volume information/)
end
end
describe "#owner=" do
it "should allow setting to the current user" do
winsec.set_owner(sids[:current_user], path)
end
it "should raise an exception when setting to a different user" do
lambda { winsec.set_owner(sids[:guest], path) }.should raise_error(Puppet::Error, /This security ID may not be assigned as the owner of this object./)
end
end
describe "#owner" do
it "it should not be empty" do
winsec.get_owner(path).should_not be_empty
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.get_owner("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
describe "#group=" do
it "should allow setting to a group the current owner is a member of" do
winsec.set_group(sids[:users], path)
end
# Unlike unix, if the user has permission to WRITE_OWNER, which the file owner has by default,
# then they can set the primary group to a group that the user does not belong to.
it "should allow setting to a group the current owner is not a member of" do
winsec.set_group(sids[:power_users], path)
end
end
describe "#group" do
it "should not be empty" do
winsec.get_group(path).should_not be_empty
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.get_group("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
it "should preserve inherited full control for SYSTEM when setting owner and group" do
# new file has SYSTEM
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
system_aces.should_not be_empty
# when running under SYSTEM account, multiple ACEs come back
# so we only care that we have at least one of these
system_aces.any? do |ace|
ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
# changing the owner/group will no longer make the SD protected
winsec.set_group(sids[:power_users], path)
winsec.set_owner(sids[:administrators], path)
system_aces.find do |ace|
ace.mask == klass::FILE_ALL_ACCESS && ace.inherited?
end.should_not be_nil
end
describe "#mode=" do
(0000..0700).step(0100) do |mode|
it "should enforce mode #{mode.to_s(8)}" do
winsec.set_mode(mode, path)
check_access(mode, path)
end
end
it "should round-trip all 128 modes that do not require deny ACEs" do
0.upto(1).each do |s|
0.upto(7).each do |u|
0.upto(u).each do |g|
0.upto(g).each do |o|
# if user is superset of group, and group superset of other, then
# no deny ace is required, and mode can be converted to win32
# access mask, and back to mode without loss of information
# (provided the owner and group are not the same)
next if ((u & g) != g) or ((g & o) != o)
mode = (s << 9 | u << 6 | g << 3 | o << 0)
winsec.set_mode(mode, path)
winsec.get_mode(path).to_s(8).should == mode.to_s(8)
end
end
end
end
end
it "should preserve full control for SYSTEM when setting mode" do
# new file has SYSTEM
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
system_aces.should_not be_empty
# when running under SYSTEM account, multiple ACEs come back
# so we only care that we have at least one of these
system_aces.any? do |ace|
ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
# changing the mode will make the SD protected
winsec.set_group(sids[:none], path)
winsec.set_mode(0600, path)
# and should have a non-inherited SYSTEM ACE(s)
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
system_aces.each do |ace|
ace.mask.should == klass::FILE_ALL_ACCESS && ! ace.inherited?
end
end
describe "for modes that require deny aces" do
it "should map everyone to group and owner" do
winsec.set_mode(0426, path)
winsec.get_mode(path).to_s(8).should == "666"
end
it "should combine user and group modes when owner and group sids are equal" do
winsec.set_group(winsec.get_owner(path), path)
winsec.set_mode(0410, path)
winsec.get_mode(path).to_s(8).should == "550"
end
end
describe "for read-only objects" do
before :each do
winsec.set_group(sids[:none], path)
winsec.set_mode(0600, path)
Puppet::Util::Windows::File.add_attributes(path, klass::FILE_ATTRIBUTE_READONLY)
(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero
end
it "should make them writable if any sid has write permission" do
winsec.set_mode(WindowsSecurityTester::S_IWUSR, path)
(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should == 0
end
it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do
winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path)
(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero
system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system])
# when running under SYSTEM account, and set_group / set_owner hasn't been called
# SYSTEM full access will be restored
system_aces.any? do |ace|
ace.mask == klass::FILE_ALL_ACCESS
end.should be_true
end
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.set_mode(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
describe "#mode" do
it "should report when extra aces are encounted" do
sd = winsec.get_security_descriptor(path)
(544..547).each do |rid|
sd.dacl.allow("S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL)
end
winsec.set_security_descriptor(path, sd)
mode = winsec.get_mode(path)
(mode & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA
end
it "should return deny aces" do
sd = winsec.get_security_descriptor(path)
sd.dacl.deny(sids[:guest], klass::FILE_GENERIC_WRITE)
winsec.set_security_descriptor(path, sd)
guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest])
guest_aces.find do |ace|
ace.type == Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE
end.should_not be_nil
end
it "should skip inherit-only ace" do
sd = winsec.get_security_descriptor(path)
dacl = Puppet::Util::Windows::AccessControlList.new
dacl.allow(
sids[:current_user], klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL
)
dacl.allow(
sids[:everyone],
klass::FILE_GENERIC_READ,
Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE
)
winsec.set_security_descriptor(path, sd)
(winsec.get_mode(path) & WindowsSecurityTester::S_IRWXO).should == 0
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.get_mode("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
describe "inherited access control entries" do
it "should be absent when the access control list is protected, and should not remove SYSTEM" do
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path)
mode = winsec.get_mode(path)
[ WindowsSecurityTester::S_IEXTRA,
WindowsSecurityTester::S_ISYSTEM_MISSING ].each do |flag|
(mode & flag).should_not == flag
end
end
it "should be present when the access control list is unprotected" do
# add a bunch of aces to the parent with permission to add children
allow = klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL
inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(parent)
sd.dacl.allow(
"S-1-1-0", #everyone
allow,
inherit
)
(544..547).each do |rid|
sd.dacl.allow(
"S-1-5-32-#{rid}",
klass::STANDARD_RIGHTS_ALL,
inherit
)
end
winsec.set_security_descriptor(parent, sd)
# unprotect child, it should inherit from parent
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path, false)
(winsec.get_mode(path) & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA
end
end
end
describe "for an administrator", :if => Puppet.features.root? do
before :each do
is_dir = Puppet::FileSystem.directory?(path)
winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path)
set_group_depending_on_current_user(path)
winsec.set_owner(sids[:guest], path)
expected_error = RUBY_VERSION =~ /^2\./ && is_dir ? Errno::EISDIR : Errno::EACCES
lambda { File.open(path, 'r') }.should raise_error(expected_error)
end
after :each do
if Puppet::FileSystem.exist?(path)
winsec.set_owner(sids[:current_user], path)
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path)
end
end
describe "#owner=" do
it "should accept a user sid" do
winsec.set_owner(sids[:admin], path)
winsec.get_owner(path).should == sids[:admin]
end
it "should accept a group sid" do
winsec.set_owner(sids[:power_users], path)
winsec.get_owner(path).should == sids[:power_users]
end
it "should raise an exception if an invalid sid is provided" do
lambda { winsec.set_owner("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/)
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.set_owner(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
describe "#group=" do
it "should accept a group sid" do
winsec.set_group(sids[:power_users], path)
winsec.get_group(path).should == sids[:power_users]
end
it "should accept a user sid" do
winsec.set_group(sids[:admin], path)
winsec.get_group(path).should == sids[:admin]
end
it "should combine owner and group rights when they are the same sid" do
winsec.set_owner(sids[:power_users], path)
winsec.set_group(sids[:power_users], path)
winsec.set_mode(0610, path)
winsec.get_owner(path).should == sids[:power_users]
winsec.get_group(path).should == sids[:power_users]
# note group execute permission added to user ace, and then group rwx value
# reflected to match
# Exclude missing system ace, since that's not relevant
(winsec.get_mode(path) & 0777).to_s(8).should == "770"
end
it "should raise an exception if an invalid sid is provided" do
lambda { winsec.set_group("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/)
end
it "should raise an exception if an invalid path is provided" do
lambda { winsec.set_group(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./)
end
end
describe "when the sid is NULL" do
it "should retrieve an empty owner sid"
it "should retrieve an empty group sid"
end
describe "when the sid refers to a deleted trustee" do
it "should retrieve the user sid" do
sid = nil
- user = Puppet::Util::Windows::ADSI::User.create("delete_me_user")
+ user = Puppet::Util::Windows::ADSI::User.create("puppet#{rand(10000)}")
user.commit
begin
sid = Puppet::Util::Windows::ADSI::User.new(user.name).sid.to_s
winsec.set_owner(sid, path)
winsec.set_mode(WindowsSecurityTester::S_IRWXU, path)
ensure
Puppet::Util::Windows::ADSI::User.delete(user.name)
end
winsec.get_owner(path).should == sid
winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXU
end
it "should retrieve the group sid" do
sid = nil
- group = Puppet::Util::Windows::ADSI::Group.create("delete_me_group")
+ group = Puppet::Util::Windows::ADSI::Group.create("puppet#{rand(10000)}")
group.commit
begin
sid = Puppet::Util::Windows::ADSI::Group.new(group.name).sid.to_s
winsec.set_group(sid, path)
winsec.set_mode(WindowsSecurityTester::S_IRWXG, path)
ensure
Puppet::Util::Windows::ADSI::Group.delete(group.name)
end
winsec.get_group(path).should == sid
winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXG
end
end
describe "#mode" do
it "should deny all access when the DACL is empty, including SYSTEM" do
sd = winsec.get_security_descriptor(path)
# don't allow inherited aces to affect the test
protect = true
new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, [], protect)
winsec.set_security_descriptor(path, new_sd)
winsec.get_mode(path).should == WindowsSecurityTester::S_ISYSTEM_MISSING
end
# REMIND: ruby crashes when trying to set a NULL DACL
# it "should allow all when it is nil" do
# winsec.set_owner(sids[:current_user], path)
# winsec.open_file(path, WindowsSecurityTester::READ_CONTROL | WindowsSecurityTester::WRITE_DAC) do |handle|
# winsec.set_security_info(handle, WindowsSecurityTester::DACL_SECURITY_INFORMATION | WindowsSecurityTester::PROTECTED_DACL_SECURITY_INFORMATION, nil)
# end
# winsec.get_mode(path).to_s(8).should == "777"
# end
end
describe "when the parent directory" do
before :each do
winsec.set_owner(sids[:current_user], parent)
winsec.set_owner(sids[:current_user], path)
winsec.set_mode(0777, path, false)
end
describe "is writable and executable" do
describe "and sticky bit is set" do
it "should allow child owner" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(01700, parent)
check_delete(path)
end
it "should allow parent owner" do
winsec.set_owner(sids[:current_user], parent)
winsec.set_group(sids[:guest], parent)
winsec.set_mode(01700, parent)
winsec.set_owner(sids[:current_user], path)
winsec.set_group(sids[:guest], path)
winsec.set_mode(0700, path)
check_delete(path)
end
it "should deny group" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(01770, parent)
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
lambda { check_delete(path) }.should raise_error(Errno::EACCES)
end
it "should deny other" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(01777, parent)
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
lambda { check_delete(path) }.should raise_error(Errno::EACCES)
end
end
describe "and sticky bit is not set" do
it "should allow child owner" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0700, parent)
check_delete(path)
end
it "should allow parent owner" do
winsec.set_owner(sids[:current_user], parent)
winsec.set_group(sids[:guest], parent)
winsec.set_mode(0700, parent)
winsec.set_owner(sids[:current_user], path)
winsec.set_group(sids[:guest], path)
winsec.set_mode(0700, path)
check_delete(path)
end
it "should allow group" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0770, parent)
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
check_delete(path)
end
it "should allow other" do
winsec.set_owner(sids[:guest], parent)
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0777, parent)
winsec.set_owner(sids[:guest], path)
winsec.set_group(sids[:current_user], path)
winsec.set_mode(0700, path)
check_delete(path)
end
end
end
describe "is not writable" do
before :each do
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0555, parent)
end
it_behaves_like "only child owner"
end
describe "is not executable" do
before :each do
winsec.set_group(sids[:current_user], parent)
winsec.set_mode(0666, parent)
end
it_behaves_like "only child owner"
end
end
end
end
end
describe "file" do
let (:parent) do
tmpdir('win_sec_test_file')
end
let (:path) do
path = File.join(parent, 'childfile')
File.new(path, 'w').close
path
end
after :each do
# allow temp files to be cleaned up
grant_everyone_full_access(parent)
end
it_behaves_like "a securable object" do
def check_access(mode, path)
if (mode & WindowsSecurityTester::S_IRUSR).nonzero?
check_read(path)
else
lambda { check_read(path) }.should raise_error(Errno::EACCES)
end
if (mode & WindowsSecurityTester::S_IWUSR).nonzero?
check_write(path)
else
lambda { check_write(path) }.should raise_error(Errno::EACCES)
end
if (mode & WindowsSecurityTester::S_IXUSR).nonzero?
lambda { check_execute(path) }.should raise_error(Errno::ENOEXEC)
else
lambda { check_execute(path) }.should raise_error(Errno::EACCES)
end
end
def check_read(path)
File.open(path, 'r').close
end
def check_write(path)
File.open(path, 'w').close
end
def check_execute(path)
Kernel.exec(path)
end
def check_delete(path)
File.delete(path)
end
end
describe "locked files" do
let (:explorer) { File.join(Dir::WINDOWS, "explorer.exe") }
it "should get the owner" do
winsec.get_owner(explorer).should match /^S-1-5-/
end
it "should get the group" do
winsec.get_group(explorer).should match /^S-1-5-/
end
it "should get the mode" do
winsec.get_mode(explorer).should == (WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG | WindowsSecurityTester::S_IEXTRA)
end
end
end
describe "directory" do
let (:parent) do
tmpdir('win_sec_test_dir')
end
let (:path) do
path = File.join(parent, 'childdir')
Dir.mkdir(path)
path
end
after :each do
# allow temp files to be cleaned up
grant_everyone_full_access(parent)
end
it_behaves_like "a securable object" do
def check_access(mode, path)
if (mode & WindowsSecurityTester::S_IRUSR).nonzero?
check_read(path)
else
lambda { check_read(path) }.should raise_error(Errno::EACCES)
end
if (mode & WindowsSecurityTester::S_IWUSR).nonzero?
check_write(path)
else
lambda { check_write(path) }.should raise_error(Errno::EACCES)
end
if (mode & WindowsSecurityTester::S_IXUSR).nonzero?
check_execute(path)
else
lambda { check_execute(path) }.should raise_error(Errno::EACCES)
end
end
def check_read(path)
Dir.entries(path)
end
def check_write(path)
Dir.mkdir(File.join(path, "subdir"))
end
def check_execute(path)
Dir.chdir(path) {}
end
def check_delete(path)
Dir.rmdir(path)
end
end
describe "inheritable aces" do
it "should be applied to child objects" do
mode640 = WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IWUSR | WindowsSecurityTester::S_IRGRP
winsec.set_mode(mode640, path)
newfile = File.join(path, "newfile.txt")
File.new(newfile, "w").close
newdir = File.join(path, "newdir")
Dir.mkdir(newdir)
[newfile, newdir].each do |p|
mode = winsec.get_mode(p)
(mode & 07777).to_s(8).should == mode640.to_s(8)
end
end
end
end
context "security descriptor" do
let(:path) { tmpfile('sec_descriptor') }
let(:read_execute) { 0x201FF }
let(:synchronize) { 0x100000 }
before :each do
FileUtils.touch(path)
end
it "preserves aces for other users" do
dacl = Puppet::Util::Windows::AccessControlList.new
sids_in_dacl = [sids[:current_user], sids[:users]]
sids_in_dacl.each do |sid|
dacl.allow(sid, read_execute)
end
sd = Puppet::Util::Windows::SecurityDescriptor.new(sids[:guest], sids[:guest], dacl, true)
winsec.set_security_descriptor(path, sd)
aces = winsec.get_security_descriptor(path).dacl.to_a
aces.map(&:sid).should == sids_in_dacl
aces.map(&:mask).all? { |mask| mask == read_execute }.should be_true
end
it "changes the sid for all aces that were assigned to the old owner" do
sd = winsec.get_security_descriptor(path)
sd.owner.should_not == sids[:guest]
sd.dacl.allow(sd.owner, read_execute)
sd.dacl.allow(sd.owner, synchronize)
sd.owner = sids[:guest]
winsec.set_security_descriptor(path, sd)
dacl = winsec.get_security_descriptor(path).dacl
aces = dacl.find_all { |ace| ace.sid == sids[:guest] }
# only non-inherited aces will be reassigned to guest, so
# make sure we find at least the two we added
aces.size.should >= 2
end
it "preserves INHERIT_ONLY_ACEs" do
# inherit only aces can only be set on directories
dir = tmpdir('inheritonlyace')
inherit_flags = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE |
Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE |
Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(dir)
sd.dacl.allow(sd.owner, klass::FILE_ALL_ACCESS, inherit_flags)
winsec.set_security_descriptor(dir, sd)
sd = winsec.get_security_descriptor(dir)
winsec.set_owner(sids[:guest], dir)
sd = winsec.get_security_descriptor(dir)
sd.dacl.find do |ace|
ace.sid == sids[:guest] && ace.inherit_only?
end.should_not be_nil
end
it "allows deny ACEs with inheritance" do
# inheritance can only be set on directories
dir = tmpdir('denyaces')
inherit_flags = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE |
Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE
sd = winsec.get_security_descriptor(dir)
sd.dacl.deny(sids[:guest], klass::FILE_ALL_ACCESS, inherit_flags)
winsec.set_security_descriptor(dir, sd)
sd = winsec.get_security_descriptor(dir)
sd.dacl.find do |ace|
ace.sid == sids[:guest] && ace.flags != 0
end.should_not be_nil
end
context "when managing mode" do
it "removes aces for sids that are neither the owner nor group" do
# add a guest ace, it's never owner or group
sd = winsec.get_security_descriptor(path)
sd.dacl.allow(sids[:guest], read_execute)
winsec.set_security_descriptor(path, sd)
# setting the mode, it should remove extra aces
winsec.set_mode(0770, path)
# make sure it's gone
dacl = winsec.get_security_descriptor(path).dacl
aces = dacl.find_all { |ace| ace.sid == sids[:guest] }
aces.should be_empty
end
end
end
end
diff --git a/spec/lib/puppet_spec/matchers.rb b/spec/lib/puppet_spec/matchers.rb
index c01d8e89d..033397cb8 100644
--- a/spec/lib/puppet_spec/matchers.rb
+++ b/spec/lib/puppet_spec/matchers.rb
@@ -1,147 +1,152 @@
require 'stringio'
########################################################################
# Backward compatibility for Jenkins outdated environment.
module RSpec
module Matchers
module BlockAliases
alias_method :to, :should unless method_defined? :to
alias_method :to_not, :should_not unless method_defined? :to_not
alias_method :not_to, :should_not unless method_defined? :not_to
end
end
end
########################################################################
# Custom matchers...
RSpec::Matchers.define :have_matching_element do |expected|
match do |actual|
actual.any? { |item| item =~ expected }
end
end
+RSpec::Matchers.define :have_matching_log do |expected|
+ match do |actual|
+ actual.map(&:to_s).any? { |item| item =~ expected }
+ end
+end
RSpec::Matchers.define :exit_with do |expected|
actual = nil
match do |block|
begin
block.call
rescue SystemExit => e
actual = e.status
end
actual and actual == expected
end
failure_message_for_should do |block|
"expected exit with code #{expected} but " +
(actual.nil? ? " exit was not called" : "we exited with #{actual} instead")
end
failure_message_for_should_not do |block|
"expected that exit would not be called with #{expected}"
end
description do
"expect exit with #{expected}"
end
end
RSpec::Matchers.define :have_printed do |expected|
case expected
when String, Regexp
expected = expected
else
expected = expected.to_s
end
chain :and_exit_with do |code|
@expected_exit_code = code
end
define_method :matches_exit_code? do |actual|
@expected_exit_code.nil? || @expected_exit_code == actual
end
define_method :matches_output? do |actual|
return false unless actual
case expected
when String
actual.include?(expected)
when Regexp
expected.match(actual)
else
raise ArgumentError, "No idea how to match a #{actual.class.name}"
end
end
match do |block|
$stderr = $stdout = StringIO.new
$stdout.set_encoding('UTF-8') if $stdout.respond_to?(:set_encoding)
begin
block.call
rescue SystemExit => e
raise unless @expected_exit_code
@actual_exit_code = e.status
ensure
$stdout.rewind
@actual = $stdout.read
$stdout = STDOUT
$stderr = STDERR
end
matches_output?(@actual) && matches_exit_code?(@actual_exit_code)
end
failure_message_for_should do |actual|
if actual.nil? then
"expected #{expected.inspect}, but nothing was printed"
else
if !@expected_exit_code.nil? && matches_output?(actual)
"expected exit with code #{@expected_exit_code} but " +
(@actual_exit_code.nil? ? " exit was not called" : "exited with #{@actual_exit_code} instead")
else
"expected #{expected.inspect} to be printed; got:\n#{actual}"
end
end
end
failure_message_for_should_not do |actual|
if @expected_exit_code && matches_exit_code?(@actual_exit_code)
"expected exit code to not be #{@actual_exit_code}"
else
"expected #{expected.inspect} to not be printed; got:\n#{actual}"
end
end
description do
"expect #{expected.inspect} to be printed" + (@expected_exit_code.nil ? '' : " with exit code #{@expected_exit_code}")
end
end
RSpec::Matchers.define :equal_attributes_of do |expected|
match do |actual|
actual.instance_variables.all? do |attr|
actual.instance_variable_get(attr) == expected.instance_variable_get(attr)
end
end
end
RSpec::Matchers.define :equal_resource_attributes_of do |expected|
match do |actual|
actual.keys do |attr|
actual[attr] == expected[attr]
end
end
end
RSpec::Matchers.define :be_one_of do |*expected|
match do |actual|
expected.include? actual
end
failure_message_for_should do |actual|
"expected #{actual.inspect} to be one of #{expected.map(&:inspect).join(' or ')}"
end
end
diff --git a/spec/unit/functions/epp_spec.rb b/spec/unit/functions/epp_spec.rb
index 382fd9548..da58748b3 100644
--- a/spec/unit/functions/epp_spec.rb
+++ b/spec/unit/functions/epp_spec.rb
@@ -1,155 +1,154 @@
-
require 'spec_helper'
describe "the epp function" do
include PuppetSpec::Files
before :all do
Puppet::Parser::Functions.autoloader.loadall
end
before :each do
Puppet[:parser] = 'future'
end
let :node do Puppet::Node.new('localhost') end
let :compiler do Puppet::Parser::Compiler.new(node) end
let :scope do Puppet::Parser::Scope.new(compiler) end
context "when accessing scope variables as $ variables" do
it "looks up the value from the scope" do
scope["what"] = "are belong"
eval_template("all your base <%= $what %> to us").should == "all your base are belong to us"
end
it "get nil accessing a variable that does not exist" do
eval_template("<%= $kryptonite == undef %>").should == "true"
end
it "get nil accessing a variable that is undef" do
scope['undef_var'] = nil
eval_template("<%= $undef_var == undef %>").should == "true"
end
it "gets shadowed variable if args are given" do
scope['phantom'] = 'of the opera'
eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true"
end
it "can use values from the enclosing scope for defaults" do
scope['phantom'] = 'of the opera'
eval_template("<%- |$phantom = $phantom| -%><%= $phantom %>").should == "of the opera"
end
it "uses the default value if the given value is undef/nil" do
eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil).should == "inside your mind"
end
it "gets shadowed variable if args are given and parameters are specified" do
scope['x'] = 'wrong one'
eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct').should == "true"
end
it "raises an error if required variable is not given" do
scope['x'] = 'wrong one'
expect do
eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct')
end.to raise_error(/no value given for required parameters x/)
end
it "raises an error if too many arguments are given" do
scope['x'] = 'wrong one'
expect do
eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus')
end.to raise_error(/Too many arguments: 2 for 1/)
end
end
context "when given an empty template" do
it "allows the template file to be empty" do
expect(eval_template("")).to eq("")
end
it "allows the template to have empty body after parameters" do
expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("")
end
end
context "when using typed parameters" do
it "allows a passed value that matches the parameter's type" do
expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true")
end
it "does not allow slurped parameters" do
expect do
eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect')
end.to raise_error(/'captures rest' - not supported in an Epp Template/)
end
it "raises an error when the passed value does not match the parameter's type" do
expect do
eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect')
end.to raise_error(/expected.*Integer.*actual.*String/m)
end
it "raises an error when the default value does not match the parameter's type" do
expect do
eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>")
end.to raise_error(/expected.*Integer.*actual.*String/m)
end
it "allows an parameter to default to undef" do
expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true")
end
end
# although never a problem with epp
it "is not interfered with by having a variable named 'string' (#14093)" do
scope['string'] = "this output should not be seen"
eval_template("some text that is static").should == "some text that is static"
end
it "has access to a variable named 'string' (#14093)" do
scope['string'] = "the string value"
eval_template("string was: <%= $string %>").should == "string was: the string value"
end
describe 'when loading from modules' do
include PuppetSpec::Files
it 'an epp template is found' do
modules_dir = dir_containing('modules', {
'testmodule' => {
'templates' => {
'the_x.epp' => 'The x is <%= $x %>'
}
}})
Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do
node.environment = env
expect(epp_function.call(scope, 'testmodule/the_x.epp', { 'x' => '3'} )).to eql("The x is 3")
end
end
end
def eval_template_with_args(content, args_hash)
file_path = tmpdir('epp_spec_content')
filename = File.join(file_path, "template.epp")
File.open(filename, "w+") { |f| f.write(content) }
Puppet::Parser::Files.stubs(:find_template).returns(filename)
epp_function.call(scope, 'template', args_hash)
end
def eval_template(content)
file_path = tmpdir('epp_spec_content')
filename = File.join(file_path, "template.epp")
File.open(filename, "w+") { |f| f.write(content) }
Puppet::Parser::Files.stubs(:find_template).returns(filename)
epp_function.call(scope, 'template')
end
def epp_function()
epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'epp')
end
end
diff --git a/spec/unit/pops/loaders/dependency_loader_spec.rb b/spec/unit/pops/loaders/dependency_loader_spec.rb
index cbdefe897..31d937766 100644
--- a/spec/unit/pops/loaders/dependency_loader_spec.rb
+++ b/spec/unit/pops/loaders/dependency_loader_spec.rb
@@ -1,61 +1,66 @@
require 'spec_helper'
require 'puppet_spec/files'
require 'puppet/pops'
require 'puppet/loaders'
describe 'dependency loader' do
include PuppetSpec::Files
let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() }
let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) }
describe 'FileBased module loader' do
it 'load something in global name space raises an error' do
module_dir = dir_containing('testmodule', {
'lib' => { 'puppet' => { 'functions' => { 'testmodule' => {
'foo.rb' => 'Puppet::Functions.create_function("foo") { def foo; end; }'
}}}}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
- dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader])
+ loader = loader_for('testmodule', module_dir)
+
expect do
- dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ loader.load_typed(typed_name(:function, 'testmodule::foo')).value
end.to raise_error(ArgumentError, /produced mis-matched name, expected 'testmodule::foo', got foo/)
end
it 'can load something in a qualified name space' do
module_dir = dir_containing('testmodule', {
'lib' => { 'puppet' => { 'functions' => { 'testmodule' => {
'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }'
}}}}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
- dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader])
- function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ loader = loader_for('testmodule', module_dir)
+
+ function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value
expect(function.class.name).to eq('testmodule::foo')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
it 'can load something in a qualified name space more than once' do
module_dir = dir_containing('testmodule', {
'lib' => { 'puppet' => { 'functions' => { 'testmodule' => {
'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }'
}}}}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
- dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader])
- function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ loader = loader_for('testmodule', module_dir)
+
+ function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value
expect(function.class.name).to eq('testmodule::foo')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
- function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value
+ function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value
expect(function.class.name).to eq('testmodule::foo')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
end
+ def loader_for(name, dir)
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, name, dir)
+ Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader])
+ end
+
def typed_name(type, name)
Puppet::Pops::Loader::Loader::TypedName.new(type, name)
end
end
diff --git a/spec/unit/pops/loaders/loader_paths_spec.rb b/spec/unit/pops/loaders/loader_paths_spec.rb
index 2cd565a8c..7f65f44c1 100644
--- a/spec/unit/pops/loaders/loader_paths_spec.rb
+++ b/spec/unit/pops/loaders/loader_paths_spec.rb
@@ -1,55 +1,43 @@
require 'spec_helper'
require 'puppet_spec/files'
require 'puppet/pops'
require 'puppet/loaders'
describe 'loader paths' do
include PuppetSpec::Files
let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() }
let(:unused_loaders) { nil }
- describe 'the relative_path_for_types method' do
- it 'produces paths to load in precendence order' do
- module_dir = dir_containing('testmodule', {
- 'functions' => {},
- 'lib' => {
- 'puppet' => {
- 'functions' => {},
- }}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1')
-
- effective_paths = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(:function, module_loader)
-
- expect(effective_paths.collect(&:generic_path)).to eq([
- File.join(module_dir, 'lib', 'puppet', 'functions')
- ])
- end
-
- it 'module loader has smart-paths that prunes unavailable paths' do
- module_dir = dir_containing('testmodule', {'lib' => {'puppet' => {'functions' => {'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }' }}}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1')
-
- effective_paths = module_loader.smart_paths.effective_paths(:function)
-
- expect(effective_paths.size).to be_eql(1)
- expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions'))
- end
-
- it 'all function smart-paths produces entries if they exist' do
- module_dir = dir_containing('testmodule', {
- 'lib' => {
- 'puppet' => {
- 'functions' => {'foo4x.rb' => 'ignored in this test'},
- }}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, unused_loaders, 'testmodule', module_dir, 'test1')
-
- effective_paths = module_loader.smart_paths.effective_paths(:function)
-
- expect(effective_paths.size).to eq(1)
- expect(module_loader.path_index.size).to eq(1)
- path_index = module_loader.path_index
- expect(path_index).to include(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))
- end
+ it 'module loader has smart-paths that prunes unavailable paths' do
+ module_dir = dir_containing('testmodule', {'lib' => {'puppet' => {'functions' =>
+ {'foo.rb' =>
+ 'Puppet::Functions.create_function("testmodule::foo") {
+ def foo; end;
+ }'
+ }
+ }}})
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, unused_loaders, 'testmodule', module_dir)
+
+ effective_paths = module_loader.smart_paths.effective_paths(:function)
+
+ expect(effective_paths.size).to be_eql(1)
+ expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions'))
+ end
+
+ it 'all function smart-paths produces entries if they exist' do
+ module_dir = dir_containing('testmodule', {
+ 'lib' => {
+ 'puppet' => {
+ 'functions' => {'foo4x.rb' => 'ignored in this test'},
+ }}})
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, unused_loaders, 'testmodule', module_dir)
+
+ effective_paths = module_loader.smart_paths.effective_paths(:function)
+
+ expect(effective_paths.size).to eq(1)
+ expect(module_loader.path_index.size).to eq(1)
+ path_index = module_loader.path_index
+ expect(path_index).to include(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))
end
end
diff --git a/spec/unit/pops/loaders/module_loaders_spec.rb b/spec/unit/pops/loaders/module_loaders_spec.rb
index 058e765de..d15a8ee0d 100644
--- a/spec/unit/pops/loaders/module_loaders_spec.rb
+++ b/spec/unit/pops/loaders/module_loaders_spec.rb
@@ -1,90 +1,90 @@
require 'spec_helper'
require 'puppet_spec/files'
require 'puppet/pops'
require 'puppet/loaders'
describe 'FileBased module loader' do
include PuppetSpec::Files
let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() }
let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) }
it 'can load a 4x function API ruby function in global name space' do
module_dir = dir_containing('testmodule', {
'lib' => {
'puppet' => {
'functions' => {
'foo4x.rb' => <<-CODE
Puppet::Functions.create_function(:foo4x) do
def foo4x()
'yay'
end
end
CODE
}
}
}
})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
function = module_loader.load_typed(typed_name(:function, 'foo4x')).value
expect(function.class.name).to eq('foo4x')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
it 'can load a 4x function API ruby function in qualified name space' do
module_dir = dir_containing('testmodule', {
'lib' => {
'puppet' => {
'functions' => {
'testmodule' => {
'foo4x.rb' => <<-CODE
Puppet::Functions.create_function('testmodule::foo4x') do
def foo4x()
'yay'
end
end
CODE
}
}
}
}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
function = module_loader.load_typed(typed_name(:function, 'testmodule::foo4x')).value
expect(function.class.name).to eq('testmodule::foo4x')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
it 'makes parent loader win over entries in child' do
module_dir = dir_containing('testmodule', {
'lib' => { 'puppet' => { 'functions' => { 'testmodule' => {
'foo.rb' => <<-CODE
Puppet::Functions.create_function('testmodule::foo') do
def foo()
'yay'
end
end
CODE
}}}}})
- module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, loaders, 'testmodule', module_dir, 'test1')
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
module_dir2 = dir_containing('testmodule2', {
'lib' => { 'puppet' => { 'functions' => { 'testmodule2' => {
'foo.rb' => <<-CODE
raise "should not get here"
CODE
}}}}})
module_loader2 = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(module_loader, loaders, 'testmodule2', module_dir2, 'test2')
function = module_loader2.load_typed(typed_name(:function, 'testmodule::foo')).value
expect(function.class.name).to eq('testmodule::foo')
expect(function.is_a?(Puppet::Functions::Function)).to eq(true)
end
def typed_name(type, name)
Puppet::Pops::Loader::Loader::TypedName.new(type, name)
end
end
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
index c61ea1c4f..24449ef9d 100755
--- a/spec/unit/provider/package/rpm_spec.rb
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -1,390 +1,471 @@
#! /usr/bin/env ruby
require 'spec_helper'
provider_class = Puppet::Type.type(:package).provider(:rpm)
describe provider_class do
let (:packages) do
<<-RPM_OUTPUT
cracklib-dicts 0 2.8.9 3.3 x86_64
basesystem 0 8.0 5.1.1.el5.centos noarch
chkconfig 0 1.3.30.2 2.el5 x86_64
myresource 0 1.2.3.4 5.el4 noarch
mysummaryless 0 1.2.3.4 5.el4 noarch
RPM_OUTPUT
end
let(:resource_name) { 'myresource' }
let(:resource) do
Puppet::Type.type(:package).new(
:name => resource_name,
:ensure => :installed,
:provider => 'rpm'
)
end
let(:provider) do
provider = provider_class.new
provider.resource = resource
provider
end
let(:nevra_format) { %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\\n} }
let(:execute_options) do
{:failonfail => true, :combine => true, :custom_environment => {}}
end
let(:rpm_version) { "RPM version 5.0.0\n" }
before(:each) do
Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm")
provider_class.stubs(:which).with("rpm").returns("/bin/rpm")
provider_class.instance_variable_set("@current_version", nil)
Puppet::Type::Package::ProviderRpm.expects(:execute).with(["/bin/rpm", "--version"]).returns(rpm_version).at_most_once
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], execute_options).returns(rpm_version).at_most_once
end
describe 'provider features' do
it { should be_versionable }
it { should be_install_options }
it { should be_uninstall_options }
it { should be_virtual_packages }
end
describe "self.instances" do
describe "with a modern version of RPM" do
it "includes all the modern flags" do
Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages)
installed_packages = provider_class.instances
end
end
describe "with a version of RPM < 4.1" do
let(:rpm_version) { "RPM version 4.0.2\n" }
it "excludes the --nosignature flag" do
Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nodigest --qf '#{nevra_format}'").yields(packages)
installed_packages = provider_class.instances
end
end
describe "with a version of RPM < 4.0.2" do
let(:rpm_version) { "RPM version 3.0.5\n" }
it "excludes the --nodigest flag" do
Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --qf '#{nevra_format}'").yields(packages)
installed_packages = provider_class.instances
end
end
it "returns an array of packages" do
Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages)
installed_packages = provider_class.instances
expect(installed_packages[0].properties).to eq(
{
:provider => :rpm,
:name => "cracklib-dicts",
:epoch => "0",
:version => "2.8.9",
:release => "3.3",
:arch => "x86_64",
:ensure => "2.8.9-3.3",
}
)
expect(installed_packages[1].properties).to eq(
{
:provider => :rpm,
:name => "basesystem",
:epoch => "0",
:version => "8.0",
:release => "5.1.1.el5.centos",
:arch => "noarch",
:ensure => "8.0-5.1.1.el5.centos",
}
)
expect(installed_packages[2].properties).to eq(
{
:provider => :rpm,
:name => "chkconfig",
:epoch => "0",
:version => "1.3.30.2",
:release => "2.el5",
:arch => "x86_64",
:ensure => "1.3.30.2-2.el5",
}
)
expect(installed_packages.last.properties).to eq(
{
:provider => :rpm,
:name => "mysummaryless",
:epoch => "0",
:version => "1.2.3.4",
:release => "5.el4",
:arch => "noarch",
:ensure => "1.2.3.4-5.el4",
}
)
end
end
describe "#install" do
let(:resource) do
Puppet::Type.type(:package).new(
:name => 'myresource',
:ensure => :installed,
:source => '/path/to/package'
)
end
describe "when not already installed" do
it "only includes the '-i' flag" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i"], '/path/to/package'], execute_options)
provider.install
end
end
describe "when installed with options" do
let(:resource) do
Puppet::Type.type(:package).new(
:name => resource_name,
:ensure => :installed,
:provider => 'rpm',
:source => '/path/to/package',
:install_options => ['-D', {'--test' => 'value'}, '-Q']
)
end
it "includes the options" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i", "-D", "--test=value", "-Q"], '/path/to/package'], execute_options)
provider.install
end
end
describe "when an older version is installed" do
before(:each) do
# Force the provider to think a version of the package is already installed
# This is real hacky. I'm sorry. --jeffweiss 25 Jan 2013
provider.instance_variable_get('@property_hash')[:ensure] = '1.2.3.3'
end
it "includes the '-U --oldpackage' flags" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-U", "--oldpackage"], '/path/to/package'], execute_options)
provider.install
end
end
end
describe "#latest" do
it "retrieves version string after querying rpm for version from source file" do
resource.expects(:[]).with(:source).returns('source-string')
Puppet::Util::Execution.expects(:execfail).with(["/bin/rpm", "-q", "--qf", nevra_format, "-p", "source-string"], Puppet::Error).returns("myresource 0 1.2.3.4 5.el4 noarch\n")
expect(provider.latest).to eq("1.2.3.4-5.el4")
end
end
describe "#uninstall" do
let(:resource) do
Puppet::Type.type(:package).new(
:name => 'myresource',
:ensure => :installed
)
end
describe "on a modern RPM" do
before(:each) do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "myresource", '--nosignature', '--nodigest', "--qf", nevra_format], execute_options).returns("myresource 0 1.2.3.4 5.el4 noarch\n")
end
let(:rpm_version) { "RPM version 4.10.0\n" }
it "includes the architecture in the package name" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-e"], 'myresource-1.2.3.4-5.el4.noarch'], execute_options).returns('').at_most_once
provider.uninstall
end
end
describe "on an ancient RPM" do
before(:each) do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "myresource", '', '', '--qf', nevra_format], execute_options).returns("myresource 0 1.2.3.4 5.el4 noarch\n")
end
let(:rpm_version) { "RPM version 3.0.6\n" }
it "excludes the architecture from the package name" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-e"], 'myresource-1.2.3.4-5.el4'], execute_options).returns('').at_most_once
provider.uninstall
end
end
describe "when uninstalled with options" do
before(:each) do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "myresource", '--nosignature', '--nodigest', "--qf", nevra_format], execute_options).returns("myresource 0 1.2.3.4 5.el4 noarch\n")
end
let(:resource) do
Puppet::Type.type(:package).new(
:name => resource_name,
:ensure => :absent,
:provider => 'rpm',
:uninstall_options => ['--nodeps']
)
end
it "includes the options" do
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-e", "--nodeps"], 'myresource-1.2.3.4-5.el4.noarch'], execute_options)
provider.uninstall
end
end
end
describe "parsing" do
def parser_test(rpm_output_string, gold_hash, number_of_debug_logs = 0)
Puppet.expects(:debug).times(number_of_debug_logs)
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", nevra_format], execute_options).returns(rpm_output_string)
expect(provider.query).to eq(gold_hash)
end
let(:resource_name) { 'name' }
let('delimiter') { ':DESC:' }
let(:package_hash) do
{
:name => 'name',
:epoch => 'epoch',
:version => 'version',
:release => 'release',
:arch => 'arch',
:provider => :rpm,
:ensure => 'version-release',
}
end
let(:line) { 'name epoch version release arch' }
['name', 'epoch', 'version', 'release', 'arch'].each do |field|
it "still parses if #{field} is replaced by delimiter" do
parser_test(
line.gsub(field, delimiter),
package_hash.merge(
field.to_sym => delimiter,
:ensure => 'version-release'.gsub(field, delimiter)
)
)
end
end
it "does not fail if line is unparseable, but issues a debug log" do
parser_test('bad data', {}, 1)
end
it "does not log or fail if rpm returns package not found" do
Puppet.expects(:debug).never
expected_args = ["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", nevra_format]
Puppet::Util::Execution.expects(:execute).with(expected_args, execute_options).raises Puppet::ExecutionFailure.new("package #{resource_name} is not installed")
expect(provider.query).to be_nil
end
it "parses virtual package" do
provider.resource[:allow_virtual] = true
expected_args = ["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", nevra_format]
Puppet::Util::Execution.expects(:execute).with(expected_args, execute_options).raises Puppet::ExecutionFailure.new("package #{resource_name} is not installed")
Puppet::Util::Execution.expects(:execute).with(expected_args + ["--whatprovides"], execute_options).returns "myresource 0 1.2.3.4 5.el4 noarch\n"
expect(provider.query).to eq({
:name => "myresource",
:epoch => "0",
:version => "1.2.3.4",
:release => "5.el4",
:arch => "noarch",
:provider => :rpm,
:ensure => "1.2.3.4-5.el4"
})
end
end
describe "#install_options" do
it "returns nil by default" do
expect(provider.install_options).to eq(nil)
end
it "returns install_options when set" do
provider.resource[:install_options] = ['-n']
expect(provider.install_options).to eq(['-n'])
end
it "returns multiple install_options when set" do
provider.resource[:install_options] = ['-L', '/opt/puppet']
expect(provider.install_options).to eq(['-L', '/opt/puppet'])
end
it 'returns install_options when set as hash' do
provider.resource[:install_options] = [{ '-Darch' => 'vax' }]
expect(provider.install_options).to eq(['-Darch=vax'])
end
it 'returns install_options when an array with hashes' do
provider.resource[:install_options] = [ '-L', { '-Darch' => 'vax' }]
expect(provider.install_options).to eq(['-L', '-Darch=vax'])
end
end
describe "#uninstall_options" do
it "returns nil by default" do
expect(provider.uninstall_options).to eq(nil)
end
it "returns uninstall_options when set" do
provider.resource[:uninstall_options] = ['-n']
expect(provider.uninstall_options).to eq(['-n'])
end
it "returns multiple uninstall_options when set" do
provider.resource[:uninstall_options] = ['-L', '/opt/puppet']
expect(provider.uninstall_options).to eq(['-L', '/opt/puppet'])
end
it 'returns uninstall_options when set as hash' do
provider.resource[:uninstall_options] = [{ '-Darch' => 'vax' }]
expect(provider.uninstall_options).to eq(['-Darch=vax'])
end
it 'returns uninstall_options when an array with hashes' do
provider.resource[:uninstall_options] = [ '-L', { '-Darch' => 'vax' }]
expect(provider.uninstall_options).to eq(['-L', '-Darch=vax'])
end
end
describe ".nodigest" do
{ '4.0' => nil,
'4.0.1' => nil,
'4.0.2' => '--nodigest',
'4.0.3' => '--nodigest',
'4.1' => '--nodigest',
'5' => '--nodigest',
}.each do |version, expected|
describe "when current version is #{version}" do
it "returns #{expected.inspect}" do
provider_class.stubs(:current_version).returns(version)
expect(provider_class.nodigest).to eq(expected)
end
end
end
end
describe ".nosignature" do
{ '4.0.3' => nil,
'4.1' => '--nosignature',
'4.1.1' => '--nosignature',
'4.2' => '--nosignature',
'5' => '--nosignature',
}.each do |version, expected|
describe "when current version is #{version}" do
it "returns #{expected.inspect}" do
provider_class.stubs(:current_version).returns(version)
expect(provider_class.nosignature).to eq(expected)
end
end
end
end
+
+ describe 'version comparison' do
+
+ # test cases munged directly from rpm's own
+ # tests/rpmvercmp.at
+ it { provider.rpmvercmp("1.0", "1.0").should == 0 }
+ it { provider.rpmvercmp("1.0", "2.0").should == -1 }
+ it { provider.rpmvercmp("2.0", "1.0").should == 1 }
+ it { provider.rpmvercmp("2.0.1", "2.0.1").should == 0 }
+ it { provider.rpmvercmp("2.0", "2.0.1").should == -1 }
+ it { provider.rpmvercmp("2.0.1", "2.0").should == 1 }
+ it { provider.rpmvercmp("2.0.1a", "2.0.1a").should == 0 }
+ it { provider.rpmvercmp("2.0.1a", "2.0.1").should == 1 }
+ it { provider.rpmvercmp("2.0.1", "2.0.1a").should == -1 }
+ it { provider.rpmvercmp("5.5p1", "5.5p1").should == 0 }
+ it { provider.rpmvercmp("5.5p1", "5.5p2").should == -1 }
+ it { provider.rpmvercmp("5.5p2", "5.5p1").should == 1 }
+ it { provider.rpmvercmp("5.5p10", "5.5p10").should == 0 }
+ it { provider.rpmvercmp("5.5p1", "5.5p10").should == -1 }
+ it { provider.rpmvercmp("5.5p10", "5.5p1").should == 1 }
+ it { provider.rpmvercmp("10xyz", "10.1xyz").should == -1 }
+ it { provider.rpmvercmp("10.1xyz", "10xyz").should == 1 }
+ it { provider.rpmvercmp("xyz10", "xyz10").should == 0 }
+ it { provider.rpmvercmp("xyz10", "xyz10.1").should == -1 }
+ it { provider.rpmvercmp("xyz10.1", "xyz10").should == 1 }
+ it { provider.rpmvercmp("xyz.4", "xyz.4").should == 0 }
+ it { provider.rpmvercmp("xyz.4", "8").should == -1 }
+ it { provider.rpmvercmp("8", "xyz.4").should == 1 }
+ it { provider.rpmvercmp("xyz.4", "2").should == -1 }
+ it { provider.rpmvercmp("2", "xyz.4").should == 1 }
+ it { provider.rpmvercmp("5.5p2", "5.6p1").should == -1 }
+ it { provider.rpmvercmp("5.6p1", "5.5p2").should == 1 }
+ it { provider.rpmvercmp("5.6p1", "6.5p1").should == -1 }
+ it { provider.rpmvercmp("6.5p1", "5.6p1").should == 1 }
+ it { provider.rpmvercmp("6.0.rc1", "6.0").should == 1 }
+ it { provider.rpmvercmp("6.0", "6.0.rc1").should == -1 }
+ it { provider.rpmvercmp("10b2", "10a1").should == 1 }
+ it { provider.rpmvercmp("10a2", "10b2").should == -1 }
+ it { provider.rpmvercmp("1.0aa", "1.0aa").should == 0 }
+ it { provider.rpmvercmp("1.0a", "1.0aa").should == -1 }
+ it { provider.rpmvercmp("1.0aa", "1.0a").should == 1 }
+ it { provider.rpmvercmp("10.0001", "10.0001").should == 0 }
+ it { provider.rpmvercmp("10.0001", "10.1").should == 0 }
+ it { provider.rpmvercmp("10.1", "10.0001").should == 0 }
+ it { provider.rpmvercmp("10.0001", "10.0039").should == -1 }
+ it { provider.rpmvercmp("10.0039", "10.0001").should == 1 }
+ it { provider.rpmvercmp("4.999.9", "5.0").should == -1 }
+ it { provider.rpmvercmp("5.0", "4.999.9").should == 1 }
+ it { provider.rpmvercmp("20101121", "20101121").should == 0 }
+ it { provider.rpmvercmp("20101121", "20101122").should == -1 }
+ it { provider.rpmvercmp("20101122", "20101121").should == 1 }
+ it { provider.rpmvercmp("2_0", "2_0").should == 0 }
+ it { provider.rpmvercmp("2.0", "2_0").should == 0 }
+ it { provider.rpmvercmp("2_0", "2.0").should == 0 }
+ it { provider.rpmvercmp("a", "a").should == 0 }
+ it { provider.rpmvercmp("a+", "a+").should == 0 }
+ it { provider.rpmvercmp("a+", "a_").should == 0 }
+ it { provider.rpmvercmp("a_", "a+").should == 0 }
+ it { provider.rpmvercmp("+a", "+a").should == 0 }
+ it { provider.rpmvercmp("+a", "_a").should == 0 }
+ it { provider.rpmvercmp("_a", "+a").should == 0 }
+ it { provider.rpmvercmp("+_", "+_").should == 0 }
+ it { provider.rpmvercmp("_+", "+_").should == 0 }
+ it { provider.rpmvercmp("_+", "_+").should == 0 }
+ it { provider.rpmvercmp("+", "_").should == 0 }
+ it { provider.rpmvercmp("_", "+").should == 0 }
+ it { provider.rpmvercmp("1.0~rc1", "1.0~rc1").should == 0 }
+ it { provider.rpmvercmp("1.0~rc1", "1.0").should == -1 }
+ it { provider.rpmvercmp("1.0", "1.0~rc1").should == 1 }
+ it { provider.rpmvercmp("1.0~rc1", "1.0~rc2").should == -1 }
+ it { provider.rpmvercmp("1.0~rc2", "1.0~rc1").should == 1 }
+ it { provider.rpmvercmp("1.0~rc1~git123", "1.0~rc1~git123").should == 0 }
+ it { provider.rpmvercmp("1.0~rc1~git123", "1.0~rc1").should == -1 }
+ it { provider.rpmvercmp("1.0~rc1", "1.0~rc1~git123").should == 1 }
+ it { provider.rpmvercmp("1.0~rc1", "1.0arc1").should == -1 }
+
+ # non-upstream test cases
+ it { provider.rpmvercmp("405", "406").should == -1 }
+ it { provider.rpmvercmp("1", "0").should == 1 }
+ end
+
end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index 20a523be9..0e88270ed 100755
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -1,308 +1,449 @@
#! /usr/bin/env ruby
require 'spec_helper'
provider_class = Puppet::Type.type(:package).provider(:yum)
describe provider_class do
let(:name) { 'mypackage' }
let(:resource) do
Puppet::Type.type(:package).new(
:name => name,
:ensure => :installed,
:provider => 'yum'
)
end
let(:provider) do
provider = provider_class.new
provider.resource = resource
provider
end
before do
provider.stubs(:yum).returns 'yum'
provider.stubs(:rpm).returns 'rpm'
provider.stubs(:get).with(:version).returns '1'
provider.stubs(:get).with(:release).returns '1'
provider.stubs(:get).with(:arch).returns 'i386'
end
describe 'provider features' do
it { should be_versionable }
it { should be_install_options }
it { should be_virtual_packages }
end
# provider should repond to the following methods
[:install, :latest, :update, :purge, :install_options].each do |method|
it "should have a(n) #{method}" do
provider.should respond_to(method)
end
end
+ describe 'package evr parsing' do
+
+ it 'should parse full simple evr' do
+ v = provider.yum_parse_evr('0:1.2.3-4.el5')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == '4.el5'
+ end
+
+ it 'should parse version only' do
+ v = provider.yum_parse_evr('1.2.3')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == nil
+ end
+
+ it 'should parse version-release' do
+ v = provider.yum_parse_evr('1.2.3-4.5.el6')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == '4.5.el6'
+ end
+
+ it 'should parse release with git hash' do
+ v = provider.yum_parse_evr('1.2.3-4.1234aefd')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == '4.1234aefd'
+ end
+
+ it 'should parse single integer versions' do
+ v = provider.yum_parse_evr('12345')
+ v[:epoch].should == '0'
+ v[:version].should == '12345'
+ v[:release].should == nil
+ end
+
+ it 'should parse text in the epoch to 0' do
+ v = provider.yum_parse_evr('foo0:1.2.3-4')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == '4'
+ end
+
+ it 'should parse revisions with text' do
+ v = provider.yum_parse_evr('1.2.3-SNAPSHOT20140107')
+ v[:epoch].should == '0'
+ v[:version].should == '1.2.3'
+ v[:release].should == 'SNAPSHOT20140107'
+ end
+
+ # test cases for PUP-682
+ it 'should parse revisions with text and numbers' do
+ v = provider.yum_parse_evr('2.2-SNAPSHOT20121119105647')
+ v[:epoch].should == '0'
+ v[:version].should == '2.2'
+ v[:release].should == 'SNAPSHOT20121119105647'
+ end
+
+ end
+
+ describe 'yum evr comparison' do
+
+ # currently passing tests
+ it 'should evaluate identical version-release as equal' do
+ v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'},
+ {:epoch => '0', :version => '1.2.3', :release => '1.el5'})
+ v.should == 0
+ end
+
+ it 'should evaluate identical version as equal' do
+ v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil},
+ {:epoch => '0', :version => '1.2.3', :release => nil})
+ v.should == 0
+ end
+
+ it 'should evaluate identical version but older release as less' do
+ v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'},
+ {:epoch => '0', :version => '1.2.3', :release => '2.el5'})
+ v.should == -1
+ end
+
+ it 'should evaluate identical version but newer release as greater' do
+ v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => '3.el5'},
+ {:epoch => '0', :version => '1.2.3', :release => '2.el5'})
+ v.should == 1
+ end
+
+ it 'should evaluate a newer epoch as greater' do
+ v = provider.yum_compareEVR({:epoch => '1', :version => '1.2.3', :release => '4.5'},
+ {:epoch => '0', :version => '1.2.3', :release => '4.5'})
+ v.should == 1
+ end
+
+ # these tests describe PUP-1244 logic yet to be implemented
+ it 'should evaluate any version as equal to the same version followed by release' do
+ v = provider.yum_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil},
+ {:epoch => '0', :version => '1.2.3', :release => '2.el5'})
+ v.should == 0
+ end
+
+ # test cases for PUP-682
+ it 'should evaluate same-length numeric revisions numerically' do
+ provider.yum_compareEVR({:epoch => '0', :version => '2.2', :release => '405'},
+ {:epoch => '0', :version => '2.2', :release => '406'}).should == -1
+ end
+
+ end
+
+ describe 'yum version segment comparison' do
+
+ it 'should treat two nil values as equal' do
+ v = provider.compare_values(nil, nil)
+ v.should == 0
+ end
+
+ it 'should treat a nil value as less than a non-nil value' do
+ v = provider.compare_values(nil, '0')
+ v.should == -1
+ end
+
+ it 'should treat a non-nil value as greater than a nil value' do
+ v = provider.compare_values('0', nil)
+ v.should == 1
+ end
+
+ it 'should pass two non-nil values on to rpmvercmp' do
+ provider.stubs(:rpmvercmp) { 0 }
+ provider.expects(:rpmvercmp).with('s1', 's2')
+ provider.compare_values('s1', 's2')
+ end
+
+ end
+
describe 'when installing' do
before(:each) do
Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm")
provider.stubs(:which).with("rpm").returns("/bin/rpm")
Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns("4.10.1\n").at_most_once
end
it 'should call yum install for :installed' do
resource.stubs(:should).with(:ensure).returns :installed
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :list, name)
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, name)
provider.install
end
it 'should use :install to update' do
provider.expects(:install)
provider.update
end
it 'should be able to set version' do
version = '1.2'
resource[:ensure] = version
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :list, name)
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, "#{name}-#{version}")
provider.stubs(:query).returns :ensure => version
provider.install
end
+ it 'should handle partial versions specified' do
+ version = '1.3.4'
+ resource[:ensure] = version
+ provider.stubs(:query).returns :ensure => '1.3.4-1.el6'
+ provider.install
+ end
+
it 'should be able to downgrade' do
current_version = '1.2'
version = '1.0'
resource[:ensure] = '1.0'
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :downgrade, "#{name}-#{version}")
provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version)
provider.install
end
it 'should accept install options' do
resource[:ensure] = :installed
resource[:install_options] = ['-t', {'-x' => 'expackage'}]
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :list, name)
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', ['-t', '-x=expackage'], :install, name)
provider.install
end
it 'allow virtual packages' do
resource[:ensure] = :installed
resource[:allow_virtual] = true
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :list, name).never
provider.expects(:yum).with('-d', '0', '-e', '0', '-y', :install, name)
provider.install
end
end
describe 'when uninstalling' do
it 'should use erase to purge' do
provider.expects(:yum).with('-y', :erase, name)
provider.purge
end
end
it 'should be versionable' do
provider.should be_versionable
end
describe 'determining the latest version available for a package' do
it "passes the value of enablerepo install_options when querying" do
resource[:install_options] = [
{'--enablerepo' => 'contrib'},
{'--enablerepo' => 'centosplus'},
]
provider.stubs(:properties).returns({:ensure => '3.4.5'})
described_class.expects(:latest_package_version).with(name, ['contrib', 'centosplus'], [])
provider.latest
end
it "passes the value of disablerepo install_options when querying" do
resource[:install_options] = [
{'--disablerepo' => 'updates'},
{'--disablerepo' => 'centosplus'},
]
provider.stubs(:properties).returns({:ensure => '3.4.5'})
described_class.expects(:latest_package_version).with(name, [], ['updates', 'centosplus'])
provider.latest
end
describe 'and a newer version is not available' do
before :each do
described_class.stubs(:latest_package_version).with(name, [], []).returns nil
end
it 'raises an error the package is not installed' do
provider.stubs(:properties).returns({:ensure => :absent})
expect {
provider.latest
}.to raise_error(Puppet::DevError, 'Tried to get latest on a missing package')
end
it 'returns version of the currently installed package' do
provider.stubs(:properties).returns({:ensure => '3.4.5'})
provider.latest.should == '3.4.5'
end
end
describe 'and a newer version is available' do
let(:latest_version) do
{
:name => name,
:epoch => '1',
:version => '2.3.4',
:release => '5',
:arch => 'i686',
}
end
it 'includes the epoch in the version string' do
described_class.stubs(:latest_package_version).with(name, [], []).returns(latest_version)
provider.latest.should == '1:2.3.4-5'
end
end
end
describe "lazy loading of latest package versions" do
before { described_class.clear }
after { described_class.clear }
let(:mypackage_version) do
{
:name => name,
:epoch => '1',
:version => '2.3.4',
:release => '5',
:arch => 'i686',
}
end
let(:mypackage_newerversion) do
{
:name => name,
:epoch => '1',
:version => '4.5.6',
:release => '7',
:arch => 'i686',
}
end
let(:latest_versions) { {name => [mypackage_version]} }
let(:enabled_versions) { {name => [mypackage_newerversion]} }
it "returns the version hash if the package was found" do
described_class.expects(:fetch_latest_versions).with([], []).once.returns(latest_versions)
version = described_class.latest_package_version(name, [], [])
expect(version).to eq(mypackage_version)
end
it "is nil if the package was not found in the query" do
described_class.expects(:fetch_latest_versions).with([], []).once.returns(latest_versions)
version = described_class.latest_package_version('nopackage', [], [])
expect(version).to be_nil
end
it "caches the package list and reuses that for subsequent queries" do
described_class.expects(:fetch_latest_versions).with([], []).once.returns(latest_versions)
2.times {
version = described_class.latest_package_version(name, [], [])
expect(version).to eq mypackage_version
}
end
it "caches separate lists for each combination of 'enablerepo' and 'disablerepo'" do
described_class.expects(:fetch_latest_versions).with([], []).once.returns(latest_versions)
described_class.expects(:fetch_latest_versions).with(['enabled'], ['disabled']).once.returns(enabled_versions)
2.times {
version = described_class.latest_package_version(name, [], [])
expect(version).to eq mypackage_version
}
2.times {
version = described_class.latest_package_version(name, ['enabled'], ['disabled'])
expect(version).to eq(mypackage_newerversion)
}
end
end
describe "querying for the latest version of all packages" do
let(:yumhelper_single_arch) do
<<-YUMHELPER_OUTPUT
* base: centos.tcpdiag.net
* extras: centos.mirrors.hoobly.com
* updates: mirrors.arsc.edu
_pkg nss-tools 0 3.14.3 4.el6_4 x86_64
_pkg pixman 0 0.26.2 5.el6_4 x86_64
_pkg myresource 0 1.2.3.4 5.el4 noarch
_pkg mysummaryless 0 1.2.3.4 5.el4 noarch
YUMHELPER_OUTPUT
end
let(:yumhelper_multi_arch) do
yumhelper_single_arch + <<-YUMHELPER_OUTPUT
_pkg nss-tools 0 3.14.3 4.el6_4 i386
_pkg pixman 0 0.26.2 5.el6_4 i386
YUMHELPER_OUTPUT
end
it "creates an entry for each line that's prefixed with '_pkg'" do
described_class.expects(:python).with([described_class::YUMHELPER]).returns(yumhelper_single_arch)
entries = described_class.fetch_latest_versions([], [])
expect(entries.keys).to include 'nss-tools'
expect(entries.keys).to include 'pixman'
expect(entries.keys).to include 'myresource'
expect(entries.keys).to include 'mysummaryless'
end
it "creates an entry for each package name and architecture" do
described_class.expects(:python).with([described_class::YUMHELPER]).returns(yumhelper_single_arch)
entries = described_class.fetch_latest_versions([], [])
expect(entries.keys).to include 'nss-tools.x86_64'
expect(entries.keys).to include 'pixman.x86_64'
expect(entries.keys).to include 'myresource.noarch'
expect(entries.keys).to include 'mysummaryless.noarch'
end
it "stores multiple entries if a package is build for multiple architectures" do
described_class.expects(:python).with([described_class::YUMHELPER]).returns(yumhelper_multi_arch)
entries = described_class.fetch_latest_versions([], [])
expect(entries.keys).to include 'nss-tools.x86_64'
expect(entries.keys).to include 'pixman.x86_64'
expect(entries.keys).to include 'nss-tools.i386'
expect(entries.keys).to include 'pixman.i386'
expect(entries['nss-tools']).to have(2).items
expect(entries['pixman']).to have(2).items
end
it "passes the repos to enable to the helper" do
described_class.expects(:python).with do |script, *args|
expect(script).to eq described_class::YUMHELPER
expect(args).to eq %w[-e updates -e centosplus]
end.returns('')
described_class.fetch_latest_versions(['updates', 'centosplus'], [])
end
it "passes the repos to disable to the helper" do
described_class.expects(:python).with do |script, *args|
expect(script).to eq described_class::YUMHELPER
expect(args).to eq %w[-d updates -d centosplus]
end.returns('')
described_class.fetch_latest_versions([], ['updates', 'centosplus'])
end
it 'passes a combination of repos to the helper' do
described_class.expects(:python).with do |script, *args|
expect(script).to eq described_class::YUMHELPER
expect(args).to eq %w[-e os -e contrib -d updates -d centosplus]
end.returns('')
described_class.fetch_latest_versions(['os', 'contrib'], ['updates', 'centosplus'])
end
end
end
diff --git a/spec/unit/provider/service/openbsd_spec.rb b/spec/unit/provider/service/openbsd_spec.rb
index ad1e50d18..b2467a5e7 100755
--- a/spec/unit/provider/service/openbsd_spec.rb
+++ b/spec/unit/provider/service/openbsd_spec.rb
@@ -1,256 +1,178 @@
#!/usr/bin/env ruby
#
# Unit testing for the OpenBSD service provider
require 'spec_helper'
provider_class = Puppet::Type.type(:service).provider(:openbsd)
describe provider_class do
before :each do
Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class
Facter.stubs(:value).with(:operatingsystem).returns :openbsd
- end
-
- let :rcscripts do
- [
- '/etc/rc.d/apmd',
- '/etc/rc.d/aucat',
- '/etc/rc.d/cron',
- '/etc/rc.d/puppetd'
- ]
+ FileTest.stubs(:file?).with('/usr/sbin/rcctl').returns true
+ FileTest.stubs(:executable?).with('/usr/sbin/rcctl').returns true
end
describe "#instances" do
it "should have an instances method" do
described_class.should respond_to :instances
end
it "should list all available services" do
- File.expects(:directory?).with('/etc/rc.d').returns true
- Dir.expects(:glob).with('/etc/rc.d/*').returns rcscripts
-
- rcscripts.each do |script|
- File.expects(:executable?).with(script).returns true
- end
-
+ described_class.stubs(:execpipe).with(['/usr/sbin/rcctl', :status]).yields File.read(my_fixture('rcctl_status'))
described_class.instances.map(&:name).should == [
- 'apmd',
- 'aucat',
- 'cron',
- 'puppetd'
+ 'accounting', 'pf', 'postgresql', 'tftpd', 'wsmoused', 'xdm',
]
end
end
describe "#start" do
it "should use the supplied start command if specified" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo'))
provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
provider.start
end
it "should start the service otherwise" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
- provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd')
+ provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], true)
provider.start
end
end
describe "#stop" do
it "should use the supplied stop command if specified" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo'))
provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
provider.stop
end
it "should stop the service otherwise" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', :stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
- provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd')
+ provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true)
provider.stop
end
end
describe "#status" do
it "should use the status command from the resource" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never
+ provider.expects(:execute).with(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true)
provider.status
end
it "should return :stopped when status command returns with a non-zero exitcode" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never
+ provider.expects(:execute).with(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true)
$CHILD_STATUS.stubs(:exitstatus).returns 3
provider.status.should == :stopped
end
it "should return :running when status command returns with a zero exitcode" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', :status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never
+ provider.expects(:execute).with(['/usr/sbin/rcctl', :status, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true)
$CHILD_STATUS.stubs(:exitstatus).returns 0
provider.status.should == :running
end
end
describe "#restart" do
it "should use the supplied restart command if specified" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo'))
- provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
+ provider.expects(:execute).with(['/usr/sbin/rcctl', '-f', :restart, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
provider.restart
end
- it "should restart the service with rc-service restart if hasrestart is true" do
+ it "should restart the service with rcctl restart if hasrestart is true" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true))
- provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
- provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd')
+ provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true)
provider.restart
end
- it "should restart the service with rc-service stop/start if hasrestart is false" do
+ it "should restart the service with rcctl stop/start if hasrestart is false" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false))
- provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never
- provider.expects(:execute).with(['/etc/rc.d/sshd', :stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
- provider.expects(:execute).with(['/etc/rc.d/sshd', '-f', :start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
- provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd')
+ provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true).never
+ provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true)
+ provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], true)
provider.restart
end
end
- describe "#parse_rc_line" do
- it "can parse a flag line with a known value" do
- output = described_class.parse_rc_line('daemon_flags=')
- output.should eq('')
- end
-
- it "can parse a flag line with a flag is wrapped in single quotes" do
- output = described_class.parse_rc_line('daemon_flags=\'\'')
- output.should eq('\'\'')
- end
-
- it "can parse a flag line with a flag is wrapped in double quotes" do
- output = described_class.parse_rc_line('daemon_flags=""')
- output.should eq('')
- end
-
- it "can parse a flag line with a trailing comment" do
- output = described_class.parse_rc_line('daemon_flags="-d" # bees')
- output.should eq('-d')
- end
-
- it "can parse a flag line with a bare word" do
- output = described_class.parse_rc_line('daemon_flags=YES')
- output.should eq('YES')
- end
-
- it "can parse a flag line with a flag that contains an equals" do
- output = described_class.parse_rc_line('daemon_flags="-Dbla -tmpdir=foo"')
- output.should eq('-Dbla -tmpdir=foo')
- end
- end
-
- describe "#pkg_scripts" do
- it "can retrieve the package_scripts array from rc.conf.local" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon cupsd"']
- expect(provider.pkg_scripts).to match_array(['dbus_daemon', 'cupsd'])
+ describe "#enabled?" do
+ it "should return :true if the service is enabled" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ described_class.stubs(:rcctl).with('status', 'sshd').returns('-6')
+ provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('-6')
+ provider.enabled?.should == :true
end
- it "returns an empty array when no pkg_scripts line is found" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ["#\n#\n#"]
- expect(provider.pkg_scripts).to match_array([])
+ it "should return :false if the service is disabled" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ described_class.stubs(:rcctl).with('status', 'sshd').returns('NO')
+ provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('NO')
+ provider.enabled?.should == :false
end
end
- describe "#pkg_scripts_append" do
- it "can append to the package_scripts array and return the result" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon"']
- provider.pkg_scripts_append.should === ['dbus_daemon', 'cupsd']
+ describe "#enable" do
+ it "should run rcctl enable to enable the service" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ described_class.stubs(:rcctl).with(:enable, 'sshd').returns('')
+ provider.expects(:rcctl).with(:enable, 'sshd')
+ provider.enable
end
- it "should not duplicate the script name" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="cupsd dbus_daemon"']
- provider.pkg_scripts_append.should === ['cupsd', 'dbus_daemon']
+ it "should run rcctl enable with flags if provided" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6'))
+ described_class.stubs(:rcctl).with(:enable, 'sshd', :flags, '-6').returns('')
+ provider.expects(:rcctl).with(:enable, 'sshd', :flags, '-6')
+ provider.enable
end
end
- describe "#pkg_scripts_remove" do
- it "can append to the package_scripts array and return the result" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon cupsd"']
- expect(provider.pkg_scripts_remove).to match_array(['dbus_daemon'])
- end
-
- it "should not remove the script from the array unless its needed" do
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:load_rcconf_local_array).returns ['pkg_scripts="dbus_daemon"']
- expect(provider.pkg_scripts_remove).to match_array(['dbus_daemon'])
+ describe "#disable" do
+ it "should run rcctl disable to disable the service" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ described_class.stubs(:rcctl).with(:disable, 'sshd').returns('')
+ provider.expects(:rcctl).with(:disable, 'sshd')
+ provider.disable
end
end
- describe "#set_content_flags" do
- it "can create the necessary content where none is provided" do
- content = []
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.set_content_flags(content,'-d').should match_array(['cupsd_flags="-d"'])
- end
-
- it "can modify the existing content" do
- content = ['cupsd_flags="-f"']
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- output = provider.set_content_flags(content,"-d")
- output.should match_array(['cupsd_flags="-d"'])
- end
-
- it "does not set empty flags for package scripts" do
- content = []
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.expects(:in_base?).returns(false)
- output = provider.set_content_flags(content,'')
- output.should match_array([nil])
- end
-
- it "does set empty flags for base scripts" do
- content = []
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'ntpd'))
- provider.expects(:in_base?).returns(true)
- output = provider.set_content_flags(content,'')
- output.should match_array(['ntpd_flags=""'])
+ describe "#flags" do
+ it "should return flags when set" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6'))
+ described_class.stubs(:rcctl).with(:status, 'sshd').returns('-6')
+ provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('-6')
+ provider.flags
end
- end
- describe "#remove_content_flags" do
- it "can remove the flags line from the requested content" do
- content = ['cupsd_flags="-d"']
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- output = provider.remove_content_flags(content)
- output.should_not match_array(['cupsd_flags="-d"'])
+ it "should return empty flags" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
+ described_class.stubs(:rcctl).with(:status, 'sshd').returns('')
+ provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('')
+ provider.flags
end
- end
- describe "#set_content_scripts" do
- it "should append to the list of scripts" do
- content = ['pkg_scripts="dbus_daemon"']
- scripts = ['dbus_daemon','cupsd']
- provider = described_class.new(Puppet::Type.type(:service).new(:name => 'cupsd'))
- provider.set_content_scripts(content,scripts).should match_array(['pkg_scripts="dbus_daemon cupsd"'])
+ it "should return flags for special services" do
+ provider = described_class.new(Puppet::Type.type(:service).new(:name => 'pf'))
+ described_class.stubs(:rcctl).with(:status, 'pf').returns('YES')
+ provider.expects(:execute).with(['/usr/sbin/rcctl', 'status', 'pf'], :failonfail => false, :combine => false, :squelch => false).returns('YES')
+ provider.flags
end
end
- describe "#in_base?" do
- it "should true if in base" do
- File.stubs(:readlines).with('/etc/rc.conf').returns(['sshd_flags=""'])
+ describe "#flags=" do
+ it "should run rcctl to set flags" do
provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd'))
- provider.in_base?.should be_true
+ described_class.stubs(:rcctl).with(:enable, 'sshd', :flags, '-4').returns('')
+ provider.expects(:rcctl).with(:enable, 'sshd', :flags, '-4')
+ provider.flags = '-4'
end
end
end
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
index bb883bdf5..3ecff6aa3 100755
--- a/spec/unit/provider/service/windows_spec.rb
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -1,183 +1,231 @@
#! /usr/bin/env ruby
#
# Unit testing for the Windows service Provider
#
require 'spec_helper'
require 'win32/service' if Puppet.features.microsoft_windows?
describe Puppet::Type.type(:service).provider(:windows), :if => Puppet.features.microsoft_windows? do
let(:name) { 'nonexistentservice' }
let(:resource) { Puppet::Type.type(:service).new(:name => name, :provider => :windows) }
let(:provider) { resource.provider }
let(:config) { Struct::ServiceConfigInfo.new }
let(:status) { Struct::ServiceStatus.new }
before :each do
# make sure we never actually execute anything (there are two execute methods)
provider.class.expects(:execute).never
provider.expects(:execute).never
Win32::Service.stubs(:config_info).with(name).returns(config)
Win32::Service.stubs(:status).with(name).returns(status)
end
describe ".instances" do
it "should enumerate all services" do
list_of_services = ['snmptrap', 'svchost', 'sshd'].map { |s| stub('service', :service_name => s) }
Win32::Service.expects(:services).returns(list_of_services)
described_class.instances.map(&:name).should =~ ['snmptrap', 'svchost', 'sshd']
end
end
describe "#start" do
before :each do
config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START)
end
it "should start the service" do
provider.expects(:net).with(:start, name)
provider.start
end
it "should raise an error if the start command fails" do
provider.expects(:net).with(:start, name).raises(Puppet::ExecutionFailure, "The service name is invalid.")
expect {
provider.start
}.to raise_error(Puppet::Error, /Cannot start #{name}, error was: The service name is invalid./)
end
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.start
+ }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/)
+ end
+
describe "when the service is disabled" do
before :each do
config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
end
it "should refuse to start if not managing enable" do
expect { provider.start }.to raise_error(Puppet::Error, /Will not start disabled service/)
end
it "should enable if managing enable and enable is true" do
resource[:enable] = :true
provider.expects(:net).with(:start, name)
Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service)
provider.start
end
it "should manual start if managing enable and enable is false" do
resource[:enable] = :false
provider.expects(:net).with(:start, name)
Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service)
provider.start
end
end
end
describe "#stop" do
it "should stop a running service" do
provider.expects(:net).with(:stop, name)
provider.stop
end
it "should raise an error if the stop command fails" do
provider.expects(:net).with(:stop, name).raises(Puppet::ExecutionFailure, 'The service name is invalid.')
expect {
provider.stop
}.to raise_error(Puppet::Error, /Cannot stop #{name}, error was: The service name is invalid./)
end
end
describe "#status" do
['stopped', 'paused', 'stop pending', 'pause pending'].each do |state|
it "should report a #{state} service as stopped" do
status.current_state = state
provider.status.should == :stopped
end
end
["running", "continue pending", "start pending" ].each do |state|
it "should report a #{state} service as running" do
status.current_state = state
provider.status.should == :running
end
end
+
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:status).with(name).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.status
+ }.to raise_error(Puppet::Error, /Cannot get status of #{name}/)
+ end
end
describe "#restart" do
it "should use the supplied restart command if specified" do
resource[:restart] = 'c:/bin/foo'
provider.expects(:execute).never
provider.expects(:execute).with(['c:/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true)
provider.restart
end
it "should restart the service" do
seq = sequence("restarting")
provider.expects(:stop).in_sequence(seq)
provider.expects(:start).in_sequence(seq)
provider.restart
end
end
describe "#enabled?" do
it "should report a service with a startup type of manual as manual" do
config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
provider.enabled?.should == :manual
end
it "should report a service with a startup type of disabled as false" do
config.start_type = Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
provider.enabled?.should == :false
end
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:config_info).with(name).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.enabled?
+ }.to raise_error(Puppet::Error, /Cannot get start type for #{name}/)
+ end
+
# We need to guard this section explicitly since rspec will always
# construct all examples, even if it isn't going to run them.
if Puppet.features.microsoft_windows?
[Win32::Service::SERVICE_AUTO_START, Win32::Service::SERVICE_BOOT_START, Win32::Service::SERVICE_SYSTEM_START].each do |start_type_const|
start_type = Win32::Service.get_start_type(start_type_const)
it "should report a service with a startup type of '#{start_type}' as true" do
config.start_type = start_type
provider.enabled?.should == :true
end
end
end
end
describe "#enable" do
it "should set service start type to Service_Auto_Start when enabled" do
Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_AUTO_START).returns(Win32::Service)
provider.enable
end
+
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.enable
+ }.to raise_error(Puppet::Error, /Cannot enable #{name}/)
+ end
end
describe "#disable" do
it "should set service start type to Service_Disabled when disabled" do
Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DISABLED).returns(Win32::Service)
provider.disable
end
+
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.disable
+ }.to raise_error(Puppet::Error, /Cannot disable #{name}/)
+ end
end
describe "#manual_start" do
it "should set service start type to Service_Demand_Start (manual) when manual" do
Win32::Service.expects(:configure).with('service_name' => name, 'start_type' => Win32::Service::SERVICE_DEMAND_START).returns(Win32::Service)
provider.manual_start
end
+
+ it "raises an error if the service doesn't exist" do
+ Win32::Service.expects(:configure).with(has_entry('service_name' => name)).raises(SystemCallError, 'OpenService')
+
+ expect {
+ provider.manual_start
+ }.to raise_error(Puppet::Error, /Cannot enable #{name}/)
+ end
end
end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 91be16ae6..e60b29cb2 100755
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -1,985 +1,1005 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/resource'
describe Puppet::Resource do
include PuppetSpec::Files
let(:basepath) { make_absolute("/somepath") }
let(:environment) { Puppet::Node::Environment.create(:testing, []) }
[:catalog, :file, :line].each do |attr|
it "should have an #{attr} attribute" do
resource = Puppet::Resource.new("file", "/my/file")
resource.should respond_to(attr)
resource.should respond_to(attr.to_s + "=")
end
end
it "should have a :title attribute" do
Puppet::Resource.new(:user, "foo").title.should == "foo"
end
it "should require the type and title" do
expect { Puppet::Resource.new }.to raise_error(ArgumentError)
end
it "should canonize types to capitalized strings" do
Puppet::Resource.new(:user, "foo").type.should == "User"
end
it "should canonize qualified types so all strings are capitalized" do
Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar"
end
it "should tag itself with its type" do
Puppet::Resource.new("file", "/f").should be_tagged("file")
end
it "should tag itself with its title if the title is a valid tag" do
Puppet::Resource.new("user", "bar").should be_tagged("bar")
end
it "should not tag itself with its title if the title is a not valid tag" do
Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar")
end
it "should allow setting of attributes" do
Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo"
Puppet::Resource.new("file", "/bar", :exported => true).should be_exported
end
it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
ref = Puppet::Resource.new(:component, "foo")
ref.type.should == "Class"
ref.title.should == "Foo"
end
it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
ref = Puppet::Resource.new(:component, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
ref = Puppet::Resource.new(nil, "yay")
ref.type.should == "Class"
ref.title.should == "Yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
ref = Puppet::Resource.new(nil, "foo::bar[yay]")
ref.type.should == "Foo::Bar"
ref.title.should == "yay"
end
it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do
ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]")
ref.type.should == "Foo::Bar"
ref.title.should =="baz[yay]"
end
it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do
ref = Puppet::Resource.new("foo::bar[baz]")
ref.type.should == "Foo::Bar"
ref.title.should =="baz"
end
it "should not interpret the title as a reference if the type is a non component or whit reference" do
ref = Puppet::Resource.new("Notify", "foo::bar[baz]")
ref.type.should == "Notify"
ref.title.should =="foo::bar[baz]"
end
it "should be able to extract its information from a Puppet::Type instance" do
ral = Puppet::Type.type(:file).new :path => basepath+"/foo"
ref = Puppet::Resource.new(ral)
ref.type.should == "File"
ref.title.should == basepath+"/foo"
end
it "should fail if the title is nil and the type is not a valid resource reference string" do
expect { Puppet::Resource.new("resource-spec-foo") }.to raise_error(ArgumentError)
end
it 'should fail if strict is set and type does not exist' do
expect { Puppet::Resource.new('resource-spec-foo', 'title', {:strict=>true}) }.to raise_error(ArgumentError, 'Invalid resource type resource-spec-foo')
end
it 'should fail if strict is set and class does not exist' do
expect { Puppet::Resource.new('Class', 'resource-spec-foo', {:strict=>true}) }.to raise_error(ArgumentError, 'Could not find declared class resource-spec-foo')
end
it "should fail if the title is a hash and the type is not a valid resource reference string" do
expect { Puppet::Resource.new({:type => "resource-spec-foo", :title => "bar"}) }.
to raise_error ArgumentError, /Puppet::Resource.new does not take a hash/
end
it "should be taggable" do
Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging)
end
it "should have an 'exported' attribute" do
resource = Puppet::Resource.new("file", "/f")
resource.exported = true
resource.exported.should == true
resource.should be_exported
end
describe "and munging its type and title" do
describe "when modeling a builtin resource" do
it "should be able to find the resource type" do
Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
end
it "should set its type to the capitalized type name" do
Puppet::Resource.new("file", "/my/file").type.should == "File"
end
end
describe "when modeling a defined resource" do
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:definition, "foo::bar")
environment.known_resource_types.add @type
end
it "should set its type to the capitalized type name" do
Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).type.should == "Foo::Bar"
end
it "should be able to find the resource type" do
Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).resource_type.should equal(@type)
end
it "should set its title to the provided title" do
Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).title.should == "/my/file"
end
end
describe "that does not exist" do
it "should set its resource type to the capitalized resource type name" do
Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar"
end
end
end
describe "when modeling a node" do
# Life's easier with nodes, because they can't be qualified.
it "should set its type to 'Node' and its title to the provided title" do
node = Puppet::Resource.new("node", "foo")
node.type.should == "Node"
node.title.should == "foo"
end
end
describe "when modeling a class" do
it "should set its type to 'Class'" do
Puppet::Resource.new("class", "foo").type.should == "Class"
end
describe "that exists" do
before do
@type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
environment.known_resource_types.add @type
end
it "should set its title to the capitalized, fully qualified resource type" do
Puppet::Resource.new("class", "foo::bar", :environment => environment).title.should == "Foo::Bar"
end
it "should be able to find the resource type" do
Puppet::Resource.new("class", "foo::bar", :environment => environment).resource_type.should equal(@type)
end
end
describe "that does not exist" do
it "should set its type to 'Class' and its title to the capitalized provided name" do
klass = Puppet::Resource.new("class", "foo::bar")
klass.type.should == "Class"
klass.title.should == "Foo::Bar"
end
end
describe "and its name is set to the empty string" do
it "should set its title to :main" do
Puppet::Resource.new("class", "").title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
environment.known_resource_types.add @type
Puppet::Resource.new("class", "", :environment => environment).title.should == :main
end
end
end
describe "and its name is set to :main" do
it "should set its title to :main" do
Puppet::Resource.new("class", :main).title.should == :main
end
describe "and a class exists whose name is the empty string" do # this was a bit tough to track down
it "should set its title to :main" do
@type = Puppet::Resource::Type.new(:hostclass, "")
environment.known_resource_types.add @type
Puppet::Resource.new("class", :main, :environment => environment).title.should == :main
end
end
end
end
end
it "should return nil when looking up resource types that don't exist" do
Puppet::Resource.new("foobar", "bar").resource_type.should be_nil
end
it "should not fail when an invalid parameter is used and strict mode is disabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
environment.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file", :environment => environment)
resource[:yay] = true
end
it "should be considered equivalent to another resource if their type and title match and no parameters are set" do
Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f")
end
it "should be considered equivalent to another resource if their type, title, and parameters are equal" do
Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
end
it "should not be considered equivalent to another resource if their type and title match but parameters are different" do
Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
end
it "should not be considered equivalent to a non-resource" do
Puppet::Resource.new("file", "/f").should_not == "foo"
end
it "should not be considered equivalent to another resource if their types do not match" do
Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f")
end
it "should not be considered equivalent to another resource if their titles do not match" do
Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f")
end
describe "when setting default parameters" do
let(:foo_node) { Puppet::Node.new('foo', :environment => environment) }
let(:compiler) { Puppet::Parser::Compiler.new(foo_node) }
let(:scope) { Puppet::Parser::Scope.new(compiler) }
def ast_string(value)
Puppet::Parser::AST::String.new({:value => value})
end
it "should fail when asked to set default values and it is not a parser resource" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("default")})
)
resource = Puppet::Resource.new("default_param", "name", :environment => environment)
lambda { resource.set_default_parameters(scope) }.should raise_error(Puppet::DevError)
end
it "should evaluate and set any default values when no value is provided" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("a_default_value")})
)
resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope)
resource.set_default_parameters(scope)
resource["a"].should == "a_default_value"
end
it "should skip attributes with no default value" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "no_default_param", :arguments => {"a" => ast_string("a_default_value")})
)
resource = Puppet::Parser::Resource.new("no_default_param", "name", :scope => scope)
lambda { resource.set_default_parameters(scope) }.should_not raise_error
end
it "should return the list of default parameters set" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_string("a_default_value")})
)
resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope)
resource.set_default_parameters(scope).should == ["a"]
end
describe "when the resource type is :hostclass" do
let(:environment_name) { "testing env" }
let(:fact_values) { { :a => 1 } }
let(:port) { Puppet::Parser::AST::String.new(:value => '80') }
let(:apache) { Puppet::Resource::Type.new(:hostclass, 'apache', :arguments => { 'port' => port }) }
before do
environment.known_resource_types.add(apache)
scope.stubs(:host).returns('host')
scope.stubs(:environment).returns(environment)
scope.stubs(:facts).returns(Puppet::Node::Facts.new("facts", fact_values))
end
context "when no value is provided" do
before(:each) do
Puppet[:binder] = true
end
let(:resource) do
Puppet::Parser::Resource.new("class", "apache", :scope => scope)
end
it "should query the data_binding terminus using a namespaced key" do
Puppet::DataBinding.indirection.expects(:find).with(
'apache::port', all_of(has_key(:environment), has_key(:variables)))
resource.set_default_parameters(scope)
end
it "should use the value from the data_binding terminus" do
Puppet::DataBinding.indirection.expects(:find).returns('443')
resource.set_default_parameters(scope)
resource[:port].should == '443'
end
it "should use the default value if the data_binding terminus returns nil" do
Puppet::DataBinding.indirection.expects(:find).returns(nil)
resource.set_default_parameters(scope)
resource[:port].should == '80'
end
it "should fail with error message about data binding on a hiera failure" do
Puppet::DataBinding.indirection.expects(:find).raises(Puppet::DataBinding::LookupError, 'Forgettabotit')
expect {
resource.set_default_parameters(scope)
}.to raise_error(Puppet::Error, /Error from DataBinding 'hiera' while looking up 'apache::port':.*Forgettabotit/)
end
end
context "when a value is provided" do
let(:port_parameter) do
Puppet::Parser::Resource::Param.new(
{ :name => 'port', :value => '8080' }
)
end
let(:resource) do
Puppet::Parser::Resource.new("class", "apache", :scope => scope,
:parameters => [port_parameter])
end
it "should not query the data_binding terminus" do
Puppet::DataBinding.indirection.expects(:find).never
resource.set_default_parameters(scope)
end
it "should not query the injector" do
# enable the injector
Puppet[:binder] = true
compiler.injector.expects(:find).never
resource.set_default_parameters(scope)
end
it "should use the value provided" do
Puppet::DataBinding.indirection.expects(:find).never
resource.set_default_parameters(scope).should == []
resource[:port].should == '8080'
end
end
end
end
describe "when validating all required parameters are present" do
it "should be able to validate that all required parameters are present" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "required_param", :arguments => {"a" => nil})
)
lambda { Puppet::Resource.new("required_param", "name", :environment => environment).validate_complete }.should raise_error(Puppet::ParseError)
end
it "should not fail when all required parameters are present" do
environment.known_resource_types.add(
Puppet::Resource::Type.new(:definition, "no_required_param")
)
resource = Puppet::Resource.new("no_required_param", "name", :environment => environment)
resource["a"] = "meh"
lambda { resource.validate_complete }.should_not raise_error
end
it "should not validate against builtin types" do
lambda { Puppet::Resource.new("file", "/bar").validate_complete }.should_not raise_error
end
end
describe "when referring to a resource with name canonicalization" do
it "should canonicalize its own name" do
res = Puppet::Resource.new("file", "/path/")
res.uniqueness_key.should == ["/path"]
res.ref.should == "File[/path/]"
end
end
describe "when running in strict mode" do
it "should be strict" do
Puppet::Resource.new("file", "/path", :strict => true).should be_strict
end
it "should fail if invalid parameters are used" do
expect { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.to raise_error
end
it "should fail if the resource type cannot be resolved" do
expect { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.to raise_error
end
end
describe "when managing parameters" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
end
it "should correctly detect when provided parameters are not valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar")
end
it "should correctly detect when provided parameters are valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode")
end
it "should correctly detect when provided parameters are not valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar")
environment.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file", :environment => environment).should_not be_valid_parameter("myparam")
end
it "should correctly detect when provided parameters are valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil})
environment.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file", :environment => environment).should be_valid_parameter("myparam")
end
it "should allow setting and retrieving of parameters" do
@resource[:foo] = "bar"
@resource[:foo].should == "bar"
end
it "should allow setting of parameters at initialization" do
Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar"
end
it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do
@resource[:foo] = "bar"
@resource["foo"].should == "bar"
end
it "should canonicalize set parameter names to treat symbols and strings equivalently" do
@resource["foo"] = "bar"
@resource[:foo].should == "bar"
end
it "should set the namevar when asked to set the name" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource[:name] = "bob"
resource[:myvar].should == "bob"
end
it "should return the namevar when asked to return the name" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource[:myvar] = "test"
resource[:name].should == "test"
end
it "should be able to set the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
expect { resource[:name] = "eh" }.to_not raise_error
end
it "should be able to return the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
resource[:name].should == "eh"
end
it "should be able to iterate over parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "bare"
params = {}
@resource.each do |key, value|
params[key] = value
end
params.should == {:foo => "bar", :fee => "bare"}
end
it "should include Enumerable" do
@resource.class.ancestors.should be_include(Enumerable)
end
it "should have a method for testing whether a parameter is included" do
@resource[:foo] = "bar"
@resource.should be_has_key(:foo)
@resource.should_not be_has_key(:eh)
end
it "should have a method for providing the list of parameters" do
@resource[:foo] = "bar"
@resource[:bar] = "foo"
keys = @resource.keys
keys.should be_include(:foo)
keys.should be_include(:bar)
end
it "should have a method for providing the number of parameters" do
@resource[:foo] = "bar"
@resource.length.should == 1
end
it "should have a method for deleting parameters" do
@resource[:foo] = "bar"
@resource.delete(:foo)
@resource[:foo].should be_nil
end
it "should have a method for testing whether the parameter list is empty" do
@resource.should be_empty
@resource[:foo] = "bar"
@resource.should_not be_empty
end
it "should be able to produce a hash of all existing parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "yay"
hash = @resource.to_hash
hash[:foo].should == "bar"
hash[:fee].should == "yay"
end
it "should not provide direct access to the internal parameters hash when producing a hash" do
hash = @resource.to_hash
hash[:foo] = "bar"
@resource[:foo].should be_nil
end
it "should use the title as the namevar to the hash if no namevar is present" do
resource = Puppet::Resource.new("user", "bob")
Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar]
resource.to_hash[:myvar].should == "bob"
end
it "should set :name to the title if :name is not present for non-builtin types" do
krt = Puppet::Resource::TypeCollection.new("myenv")
krt.add Puppet::Resource::Type.new(:definition, :foo)
resource = Puppet::Resource.new :foo, "bar"
resource.stubs(:known_resource_types).returns krt
resource.to_hash[:name].should == "bar"
end
end
describe "when serializing a native type" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
@resource["one"] = "test"
@resource["two"] = "other"
end
it "should produce an equivalent yaml object" do
text = @resource.render('yaml')
newresource = Puppet::Resource.convert_from('yaml', text)
newresource.should equal_resource_attributes_of @resource
end
end
describe "when serializing a defined type" do
before do
type = Puppet::Resource::Type.new(:definition, "foo::bar")
environment.known_resource_types.add type
@resource = Puppet::Resource.new('foo::bar', 'xyzzy', :environment => environment)
@resource['one'] = 'test'
@resource['two'] = 'other'
@resource.resource_type
end
it "doesn't include transient instance variables (#4506)" do
expect(@resource.to_yaml_properties).to_not include :@rstype
end
it "produces an equivalent yaml object" do
text = @resource.render('yaml')
newresource = Puppet::Resource.convert_from('yaml', text)
newresource.should equal_resource_attributes_of @resource
end
end
describe "when converting to a RAL resource" do
it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
resource = Puppet::Resource.new("file", basepath+"/my/file")
result = resource.to_ral
result.must be_instance_of(Puppet::Type.type(:file))
result[:path].should == basepath+"/my/file"
end
it "should convert to a component instance if the resource type is not of a builtin type" do
resource = Puppet::Resource.new("foobar", "somename")
result = resource.to_ral
result.must be_instance_of(Puppet::Type.type(:component))
result.title.should == "Foobar[somename]"
end
end
describe "when converting to puppet code" do
before do
@resource = Puppet::Resource.new("one::two", "/my/file",
:parameters => {
:noop => true,
:foo => %w{one two},
:ensure => 'present',
}
)
end
it "should align, sort and add trailing commas to attributes with ensure first" do
@resource.to_manifest.should == <<-HEREDOC.gsub(/^\s{8}/, '').gsub(/\n$/, '')
one::two { '/my/file':
ensure => 'present',
foo => ['one', 'two'],
noop => 'true',
}
HEREDOC
end
end
+ describe "when converting to Yaml for Hiera" do
+ before do
+ @resource = Puppet::Resource.new("one::two", "/my/file",
+ :parameters => {
+ :noop => true,
+ :foo => %w{one two},
+ :ensure => 'present',
+ }
+ )
+ end
+
+ it "should align and sort to attributes with ensure first" do
+ @resource.to_hierayaml.should == <<-HEREDOC.gsub(/^\s{8}/, '')
+ /my/file:
+ ensure: 'present'
+ foo : ['one', 'two']
+ noop : 'true'
+ HEREDOC
+ end
+ end
describe "when converting to pson" do
def pson_output_should
@resource.class.expects(:pson_create).with { |hash| yield hash }
end
it "should include the pson util module" do
Puppet::Resource.singleton_class.ancestors.should be_include(Puppet::Util::Pson)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its type to the provided type" do
Puppet::Resource.from_data_hash(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_data_hash(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo"
end
it "should include all tags from the resource" do
resource = Puppet::Resource.new("File", "/foo")
resource.tag("yay")
Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).tags.should == resource.tags
end
it "should include the file if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.file = "/my/file"
Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).file.should == "/my/file"
end
it "should include the line if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.line = 50
Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).line.should == 50
end
it "should include the 'exported' value if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.exported = true
Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_true
end
it "should set 'exported' to false if no value is set" do
resource = Puppet::Resource.new("File", "/foo")
Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson)).exported?.should be_false
end
it "should set all of its parameters as the 'parameters' entry" do
resource = Puppet::Resource.new("File", "/foo")
resource[:foo] = %w{bar eh}
resource[:fee] = %w{baz}
result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson))
result["foo"].should == %w{bar eh}
result["fee"].should == %w{baz}
end
it "should serialize relationships as reference strings" do
resource = Puppet::Resource.new("File", "/foo")
resource[:requires] = Puppet::Resource.new("File", "/bar")
result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson))
result[:requires].should == "File[/bar]"
end
it "should serialize multiple relationships as arrays of reference strings" do
resource = Puppet::Resource.new("File", "/foo")
resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")]
result = Puppet::Resource.from_data_hash(PSON.parse(resource.to_pson))
result[:requires].should == [ "File[/bar]", "File[/baz]" ]
end
end
describe "when converting from pson" do
def pson_result_should
Puppet::Resource.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'type' => "file",
'title' => basepath+"/yay",
}
end
it "should set its type to the provided type" do
Puppet::Resource.from_data_hash(@data).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_data_hash(@data).title.should == basepath+"/yay"
end
it "should tag the resource with any provided tags" do
@data['tags'] = %w{foo bar}
resource = Puppet::Resource.from_data_hash(@data)
resource.tags.should be_include("foo")
resource.tags.should be_include("bar")
end
it "should set its file to the provided file" do
@data['file'] = "/foo/bar"
Puppet::Resource.from_data_hash(@data).file.should == "/foo/bar"
end
it "should set its line to the provided line" do
@data['line'] = 50
Puppet::Resource.from_data_hash(@data).line.should == 50
end
it "should 'exported' to true if set in the pson data" do
@data['exported'] = true
Puppet::Resource.from_data_hash(@data).exported.should be_true
end
it "should 'exported' to false if not set in the pson data" do
Puppet::Resource.from_data_hash(@data).exported.should be_false
end
it "should fail if no title is provided" do
@data.delete('title')
expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError)
end
it "should fail if no type is provided" do
@data.delete('type')
expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError)
end
it "should set each of the provided parameters" do
@data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}}
resource = Puppet::Resource.from_data_hash(@data)
resource['foo'].should == %w{one two}
resource['fee'].should == %w{three four}
end
it "should convert single-value array parameters to normal values" do
@data['parameters'] = {'foo' => %w{one}}
resource = Puppet::Resource.from_data_hash(@data)
resource['foo'].should == %w{one}
end
end
it "implements copy_as_resource" do
resource = Puppet::Resource.new("file", "/my/file")
resource.copy_as_resource.should == resource
end
describe "because it is an indirector model" do
it "should include Puppet::Indirector" do
Puppet::Resource.should be_is_a(Puppet::Indirector)
end
it "should have a default terminus" do
Puppet::Resource.indirection.terminus_class.should be
end
it "should have a name" do
Puppet::Resource.new("file", "/my/file").name.should == "File//my/file"
end
end
describe "when resolving resources with a catalog" do
it "should resolve all resources using the catalog" do
catalog = mock 'catalog'
resource = Puppet::Resource.new("foo::bar", "yay")
resource.catalog = catalog
catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource)
resource.resolve.should == :myresource
end
end
describe "when generating the uniqueness key" do
it "should include all of the key_attributes in alphabetical order by attribute name" do
Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path]
Puppet::Type.type(:file).stubs(:title_patterns).returns(
[ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ]
)
res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'})
res.uniqueness_key.should == [ nil, 'root', '/my/file']
end
end
describe '#parse_title' do
describe 'with a composite namevar' do
before do
Puppet::Type.newtype(:composite) do
newparam(:name)
newparam(:value)
# Configure two title patterns to match a title that is either
# separated with a colon or exclamation point. The first capture
# will be used for the :name param, and the second capture will be
# used for the :value param.
def self.title_patterns
identity = lambda {|x| x }
reverse = lambda {|x| x.reverse }
[
[
/^(.*?):(.*?)$/,
[
[:name, identity],
[:value, identity],
]
],
[
/^(.*?)!(.*?)$/,
[
[:name, reverse],
[:value, reverse],
]
],
]
end
end
end
describe "with no matching title patterns" do
subject { Puppet::Resource.new(:composite, 'unmatching title')}
it "should raise an exception if no title patterns match" do
expect do
subject.to_hash
end.to raise_error(Puppet::Error, /No set of title patterns matched/)
end
end
describe "with a matching title pattern" do
subject { Puppet::Resource.new(:composite, 'matching:title') }
it "should not raise an exception if there was a match" do
expect do
subject.to_hash
end.to_not raise_error
end
it "should set the resource parameters from the parsed title values" do
h = subject.to_hash
h[:name].should == 'matching'
h[:value].should == 'title'
end
end
describe "and multiple title patterns" do
subject { Puppet::Resource.new(:composite, 'matching!title') }
it "should use the first title pattern that matches" do
h = subject.to_hash
h[:name].should == 'gnihctam'
h[:value].should == 'eltit'
end
end
end
end
describe "#prune_parameters" do
before do
Puppet.newtype('blond') do
newproperty(:ensure)
newproperty(:height)
newproperty(:weight)
newproperty(:sign)
newproperty(:friends)
newparam(:admits_to_dying_hair)
newparam(:admits_to_age)
newparam(:name)
end
end
it "should strip all parameters and strip properties that are nil, empty or absent except for ensure" do
resource = Puppet::Resource.new("blond", "Bambi", :parameters => {
:ensure => 'absent',
:height => '',
:weight => 'absent',
:friends => [],
:admits_to_age => true,
:admits_to_dying_hair => false
})
pruned_resource = resource.prune_parameters
pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:ensure => 'absent'})
end
it "should leave parameters alone if in parameters_to_include" do
resource = Puppet::Resource.new("blond", "Bambi", :parameters => {
:admits_to_age => true,
:admits_to_dying_hair => false
})
pruned_resource = resource.prune_parameters(:parameters_to_include => [:admits_to_dying_hair])
pruned_resource.should == Puppet::Resource.new("blond", "Bambi", :parameters => {:admits_to_dying_hair => false})
end
it "should leave properties if not nil, absent or empty" do
resource = Puppet::Resource.new("blond", "Bambi", :parameters => {
:ensure => 'silly',
:height => '7 ft 5 in',
:friends => ['Oprah'],
})
pruned_resource = resource.prune_parameters
pruned_resource.should ==
resource = Puppet::Resource.new("blond", "Bambi", :parameters => {
:ensure => 'silly',
:height => '7 ft 5 in',
:friends => ['Oprah'],
})
end
end
end
diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb
index 0a1cee2bd..c40a01bbd 100755
--- a/spec/unit/settings_spec.rb
+++ b/spec/unit/settings_spec.rb
@@ -1,1883 +1,1903 @@
#! /usr/bin/env ruby
require 'spec_helper'
require 'ostruct'
require 'puppet/settings/errors'
+require 'puppet_spec/files'
+require 'matchers/resource'
describe Puppet::Settings do
include PuppetSpec::Files
+ include Matchers::Resource
let(:main_config_file_default_location) do
File.join(Puppet::Util::RunMode[:master].conf_dir, "puppet.conf")
end
let(:user_config_file_default_location) do
File.join(Puppet::Util::RunMode[:user].conf_dir, "puppet.conf")
end
describe "when specifying defaults" do
before do
@settings = Puppet::Settings.new
end
it "should start with no defined parameters" do
@settings.params.length.should == 0
end
it "should not allow specification of default values associated with a section as an array" do
expect {
@settings.define_settings(:section, :myvalue => ["defaultval", "my description"])
}.to raise_error
end
it "should not allow duplicate parameter specifications" do
@settings.define_settings(:section, :myvalue => { :default => "a", :desc => "b" })
lambda { @settings.define_settings(:section, :myvalue => { :default => "c", :desc => "d" }) }.should raise_error(ArgumentError)
end
it "should allow specification of default values associated with a section as a hash" do
@settings.define_settings(:section, :myvalue => {:default => "defaultval", :desc => "my description"})
end
it "should consider defined parameters to be valid" do
@settings.define_settings(:section, :myvalue => { :default => "defaultval", :desc => "my description" })
@settings.valid?(:myvalue).should be_true
end
it "should require a description when defaults are specified with a hash" do
lambda { @settings.define_settings(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError)
end
it "should support specifying owner, group, and mode when specifying files" do
@settings.define_settings(:section, :myvalue => {:type => :file, :default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"})
end
it "should support specifying a short name" do
@settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
end
it "should support specifying the setting type" do
@settings.define_settings(:section, :myvalue => {:default => "/w", :desc => "b", :type => :string})
@settings.setting(:myvalue).should be_instance_of(Puppet::Settings::StringSetting)
end
it "should fail if an invalid setting type is specified" do
lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.should raise_error(ArgumentError)
end
it "should fail when short names conflict" do
@settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError)
end
end
describe "when initializing application defaults do" do
let(:default_values) do
values = {}
PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key|
values[key] = 'default value'
end
values
end
before do
@settings = Puppet::Settings.new
@settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS)
end
it "should fail if the app defaults hash is missing any required values" do
incomplete_default_values = default_values.reject { |key, _| key == :confdir }
expect {
@settings.initialize_app_defaults(default_values.reject { |key, _| key == :confdir })
}.to raise_error(Puppet::Settings::SettingsError)
end
# ultimately I'd like to stop treating "run_mode" as a normal setting, because it has so many special
# case behaviors / uses. However, until that time... we need to make sure that our private run_mode=
# setter method gets properly called during app initialization.
it "sets the preferred run mode when initializing the app defaults" do
@settings.initialize_app_defaults(default_values.merge(:run_mode => :master))
@settings.preferred_run_mode.should == :master
end
end
describe "#call_hooks_deferred_to_application_initialization" do
let(:good_default) { "yay" }
let(:bad_default) { "$doesntexist" }
before(:each) do
@settings = Puppet::Settings.new
end
describe "when ignoring dependency interpolation errors" do
let(:options) { {:ignore_interpolation_dependency_errors => true} }
describe "if interpolation error" do
it "should not raise an error" do
hook_values = []
@settings.define_settings(:section, :badhook => {:default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }})
expect do
@settings.send(:call_hooks_deferred_to_application_initialization, options)
end.to_not raise_error
end
end
describe "if no interpolation error" do
it "should not raise an error" do
hook_values = []
@settings.define_settings(:section, :goodhook => {:default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }})
expect do
@settings.send(:call_hooks_deferred_to_application_initialization, options)
end.to_not raise_error
end
end
end
describe "when not ignoring dependency interpolation errors" do
[ {}, {:ignore_interpolation_dependency_errors => false}].each do |options|
describe "if interpolation error" do
it "should raise an error" do
hook_values = []
@settings.define_settings(
:section,
:badhook => {
:default => bad_default,
:desc => "boo",
:call_hook => :on_initialize_and_write,
:hook => lambda { |v| hook_values << v }
}
)
expect do
@settings.send(:call_hooks_deferred_to_application_initialization, options)
end.to raise_error(Puppet::Settings::InterpolationError)
end
it "should contain the setting name in error message" do
hook_values = []
@settings.define_settings(
:section,
:badhook => {
:default => bad_default,
:desc => "boo",
:call_hook => :on_initialize_and_write,
:hook => lambda { |v| hook_values << v }
}
)
expect do
@settings.send(:call_hooks_deferred_to_application_initialization, options)
end.to raise_error(Puppet::Settings::InterpolationError, /badhook/)
end
end
describe "if no interpolation error" do
it "should not raise an error" do
hook_values = []
@settings.define_settings(
:section,
:goodhook => {
:default => good_default,
:desc => "boo",
:call_hook => :on_initialize_and_write,
:hook => lambda { |v| hook_values << v }
}
)
expect do
@settings.send(:call_hooks_deferred_to_application_initialization, options)
end.to_not raise_error
end
end
end
end
end
describe "when setting values" do
before do
@settings = Puppet::Settings.new
@settings.define_settings :main, :myval => { :default => "val", :desc => "desc" }
@settings.define_settings :main, :bool => { :type => :boolean, :default => true, :desc => "desc" }
end
it "should provide a method for setting values from other objects" do
@settings[:myval] = "something else"
@settings[:myval].should == "something else"
end
it "should support a getopt-specific mechanism for setting values" do
@settings.handlearg("--myval", "newval")
@settings[:myval].should == "newval"
end
it "should support a getopt-specific mechanism for turning booleans off" do
@settings.override_default(:bool, true)
@settings.handlearg("--no-bool", "")
@settings[:bool].should == false
end
it "should support a getopt-specific mechanism for turning booleans on" do
# Turn it off first
@settings.override_default(:bool, false)
@settings.handlearg("--bool", "")
@settings[:bool].should == true
end
it "should consider a cli setting with no argument to be a boolean" do
# Turn it off first
@settings.override_default(:bool, false)
@settings.handlearg("--bool")
@settings[:bool].should == true
end
it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do
@settings.override_default(:myval, "bob")
@settings.handlearg("--myval", "")
@settings[:myval].should == ""
end
it "should consider a cli setting with a boolean as an argument to be a boolean" do
# Turn it off first
@settings.override_default(:bool, false)
@settings.handlearg("--bool", "true")
@settings[:bool].should == true
end
it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do
@settings.override_default(:myval, "bob")
@settings.handlearg("--no-myval", "")
@settings[:myval].should == ""
end
it "should flag string settings from the CLI" do
@settings.handlearg("--myval", "12")
@settings.set_by_cli?(:myval).should be_true
end
it "should flag bool settings from the CLI" do
@settings.handlearg("--bool")
@settings.set_by_cli?(:bool).should be_true
end
it "should not flag settings memory as from CLI" do
@settings[:myval] = "12"
@settings.set_by_cli?(:myval).should be_false
end
describe "setbycli" do
it "should generate a deprecation warning" do
Puppet.expects(:deprecation_warning).at_least(1)
@settings.setting(:myval).setbycli = true
end
it "should set the value" do
@settings[:myval] = "blah"
@settings.setting(:myval).setbycli = true
@settings.set_by_cli?(:myval).should be_true
end
it "should raise error if trying to unset value" do
@settings.handlearg("--myval", "blah")
expect do
@settings.setting(:myval).setbycli = nil
end.to raise_error(ArgumentError, /unset/)
end
end
it "should clear the cache when setting getopt-specific values" do
@settings.define_settings :mysection,
:one => { :default => "whah", :desc => "yay" },
:two => { :default => "$one yay", :desc => "bah" }
@settings.expects(:unsafe_flush_cache)
@settings[:two].should == "whah yay"
@settings.handlearg("--one", "else")
@settings[:two].should == "else yay"
end
it "should clear the cache when the preferred_run_mode is changed" do
@settings.expects(:flush_cache)
@settings.preferred_run_mode = :master
end
it "should not clear other values when setting getopt-specific values" do
@settings[:myval] = "yay"
@settings.handlearg("--no-bool", "")
@settings[:myval].should == "yay"
end
it "should clear the list of used sections" do
@settings.expects(:clearused)
@settings[:myval] = "yay"
end
describe "call_hook" do
Puppet::Settings::StringSetting.available_call_hook_values.each do |val|
describe "when :#{val}" do
describe "and definition invalid" do
it "should raise error if no hook defined" do
expect do
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val})
end.to raise_error(ArgumentError, /no :hook/)
end
it "should include the setting name in the error message" do
expect do
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val})
end.to raise_error(ArgumentError, /for :hooker/)
end
end
describe "and definition valid" do
before(:each) do
hook_values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }})
end
it "should call the hook when value written" do
@settings.setting(:hooker).expects(:handle).with("something").once
@settings[:hooker] = "something"
end
end
end
end
it "should have a default value of :on_write_only" do
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }})
@settings.setting(:hooker).call_hook.should == :on_write_only
end
describe "when nil" do
it "should generate a warning" do
Puppet.expects(:warning)
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }})
end
it "should use default" do
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }})
@settings.setting(:hooker).call_hook.should == :on_write_only
end
end
describe "when invalid" do
it "should raise an error" do
expect do
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :foo, :hook => lambda { |v| hook_values << v }})
end.to raise_error(ArgumentError, /invalid.*call_hook/i)
end
end
describe "when :on_define_and_write" do
it "should call the hook at definition" do
hook_values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| hook_values << v }})
@settings.setting(:hooker).call_hook.should == :on_define_and_write
hook_values.should == %w{yay}
end
end
describe "when :on_initialize_and_write" do
before(:each) do
@hook_values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| @hook_values << v }})
end
it "should not call the hook at definition" do
@hook_values.should == []
@hook_values.should_not == %w{yay}
end
it "should call the hook at initialization" do
app_defaults = {}
Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key|
app_defaults[key] = "foo"
end
app_defaults[:run_mode] = :user
@settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS)
@settings.setting(:hooker).expects(:handle).with("yay").once
@settings.initialize_app_defaults app_defaults
end
end
end
describe "call_on_define" do
[true, false].each do |val|
describe "to #{val}" do
it "should generate a deprecation warning" do
Puppet.expects(:deprecation_warning)
values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_on_define => val, :hook => lambda { |v| values << v }})
end
it "should should set call_hook" do
values = []
name = "hooker_#{val}".to_sym
@settings.define_settings(:section, name => {:default => "yay", :desc => "boo", :call_on_define => val, :hook => lambda { |v| values << v }})
@settings.setting(name).call_hook.should == :on_define_and_write if val
@settings.setting(name).call_hook.should == :on_write_only unless val
end
end
end
end
it "should call passed blocks when values are set" do
values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }})
values.should == []
@settings[:hooker] = "something"
values.should == %w{something}
end
it "should call passed blocks when values are set via the command line" do
values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }})
values.should == []
@settings.handlearg("--hooker", "yay")
values.should == %w{yay}
end
it "should provide an option to call passed blocks during definition" do
values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }})
values.should == %w{yay}
end
it "should pass the fully interpolated value to the hook when called on definition" do
values = []
@settings.define_settings(:section, :one => { :default => "test", :desc => "a" })
@settings.define_settings(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }})
values.should == %w{test/yay}
end
it "should munge values using the setting-specific methods" do
@settings[:bool] = "false"
@settings[:bool].should == false
end
it "should prefer values set in ruby to values set on the cli" do
@settings[:myval] = "memarg"
@settings.handlearg("--myval", "cliarg")
@settings[:myval].should == "memarg"
end
it "should clear the list of environments" do
Puppet::Node::Environment.expects(:clear).at_least(1)
@settings[:myval] = "memarg"
end
it "should raise an error if we try to set a setting that hasn't been defined'" do
lambda{
@settings[:why_so_serious] = "foo"
}.should raise_error(ArgumentError, /unknown setting/)
end
it "allows overriding cli args based on the cli-set value" do
@settings.handlearg("--myval", "cliarg")
@settings.set_value(:myval, "modified #{@settings[:myval]}", :cli)
expect(@settings[:myval]).to eq("modified cliarg")
end
end
describe "when returning values" do
before do
@settings = Puppet::Settings.new
@settings.define_settings :section,
:config => { :type => :file, :default => "/my/file", :desc => "eh" },
:one => { :default => "ONE", :desc => "a" },
:two => { :default => "$one TWO", :desc => "b"},
:three => { :default => "$one $two THREE", :desc => "c"},
:four => { :default => "$two $three FOUR", :desc => "d"},
:five => { :default => nil, :desc => "e" }
Puppet::FileSystem.stubs(:exist?).returns true
end
describe "call_on_define" do
it "should generate a deprecation warning" do
Puppet.expects(:deprecation_warning)
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }})
@settings.setting(:hooker).call_on_define
end
Puppet::Settings::StringSetting.available_call_hook_values.each do |val|
it "should match value for call_hook => :#{val}" do
hook_values = []
@settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }})
@settings.setting(:hooker).call_on_define.should == @settings.setting(:hooker).call_hook_on_define?
end
end
end
it "should provide a mechanism for returning set values" do
@settings[:one] = "other"
@settings[:one].should == "other"
end
it "setting a value to nil causes it to return to its default" do
default_values = { :one => "skipped value" }
[:logdir, :confdir, :vardir].each do |key|
default_values[key] = 'default value'
end
@settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS
@settings.initialize_app_defaults(default_values)
@settings[:one] = "value will disappear"
@settings[:one] = nil
@settings[:one].should == "ONE"
end
it "should interpolate default values for other parameters into returned parameter values" do
@settings[:one].should == "ONE"
@settings[:two].should == "ONE TWO"
@settings[:three].should == "ONE ONE TWO THREE"
end
it "should interpolate default values that themselves need to be interpolated" do
@settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR"
end
it "should provide a method for returning uninterpolated values" do
@settings[:two] = "$one tw0"
@settings.uninterpolated_value(:two).should == "$one tw0"
@settings.uninterpolated_value(:four).should == "$two $three FOUR"
end
it "should interpolate set values for other parameters into returned parameter values" do
@settings[:one] = "on3"
@settings[:two] = "$one tw0"
@settings[:three] = "$one $two thr33"
@settings[:four] = "$one $two $three f0ur"
@settings[:one].should == "on3"
@settings[:two].should == "on3 tw0"
@settings[:three].should == "on3 on3 tw0 thr33"
@settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur"
end
it "should not cache interpolated values such that stale information is returned" do
@settings[:two].should == "ONE TWO"
@settings[:one] = "one"
@settings[:two].should == "one TWO"
end
it "should not cache values such that information from one environment is returned for another environment" do
text = "[env1]\none = oneval\n[env2]\none = twoval\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings.value(:one, "env1").should == "oneval"
@settings.value(:one, "env2").should == "twoval"
end
it "should have a run_mode that defaults to user" do
@settings.preferred_run_mode.should == :user
end
it "interpolates a boolean false without raising an error" do
@settings.define_settings(:section,
:trip_wire => { :type => :boolean, :default => false, :desc => "a trip wire" },
:tripping => { :default => '$trip_wire', :desc => "once tripped if interpolated was false" })
@settings[:tripping].should == "false"
end
describe "setbycli" do
it "should generate a deprecation warning" do
@settings.handlearg("--one", "blah")
Puppet.expects(:deprecation_warning)
@settings.setting(:one).setbycli
end
it "should be true" do
@settings.handlearg("--one", "blah")
@settings.setting(:one).setbycli.should be_true
end
end
end
describe "when choosing which value to return" do
before do
@settings = Puppet::Settings.new
@settings.define_settings :section,
:config => { :type => :file, :default => "/my/file", :desc => "a" },
:one => { :default => "ONE", :desc => "a" },
:two => { :default => "TWO", :desc => "b" }
Puppet::FileSystem.stubs(:exist?).returns true
@settings.preferred_run_mode = :agent
end
it "should return default values if no values have been set" do
@settings[:one].should == "ONE"
end
it "should return values set on the cli before values set in the configuration file" do
text = "[main]\none = fileval\n"
@settings.stubs(:read_file).returns(text)
@settings.handlearg("--one", "clival")
@settings.send(:parse_config_files)
@settings[:one].should == "clival"
end
it "should return values set in the mode-specific section before values set in the main section" do
text = "[main]\none = mainval\n[agent]\none = modeval\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "modeval"
end
it "should not return values outside of its search path" do
text = "[other]\none = oval\n"
file = "/some/file"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "ONE"
end
it "should return values in a specified environment" do
text = "[env]\none = envval\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings.value(:one, "env").should == "envval"
end
it 'should use the current environment for $environment' do
@settings.define_settings :main, :myval => { :default => "$environment/foo", :desc => "mydocs" }
@settings.value(:myval, "myenv").should == "myenv/foo"
end
it "should interpolate found values using the current environment" do
text = "[main]\none = mainval\n[myname]\none = nameval\ntwo = $one/two\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings.value(:two, "myname").should == "nameval/two"
end
it "should return values in a specified environment before values in the main or name sections" do
text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings.value(:one, "env").should == "envval"
end
context "when interpolating a dynamic environments setting" do
let(:dynamic_manifestdir) { "manifestdir=/somewhere/$environment/manifests" }
let(:environment) { "environment=anenv" }
before(:each) do
@settings.define_settings :main,
:manifestdir => { :default => "/manifests", :desc => "manifestdir setting" },
:environment => { :default => "production", :desc => "environment setting" }
end
it "interpolates default environment when no environment specified" do
text = <<-EOF
[main]
#{dynamic_manifestdir}
EOF
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
expect(@settings.value(:manifestdir)).to eq("/somewhere/production/manifests")
end
it "interpolates the set environment when no environment specified" do
text = <<-EOF
[main]
#{dynamic_manifestdir}
#{environment}
EOF
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
expect(@settings.value(:manifestdir)).to eq("/somewhere/anenv/manifests")
end
end
end
describe "when locating config files" do
before do
@settings = Puppet::Settings.new
end
describe "when root" do
it "should look for the main config file default location config settings haven't been overridden'" do
Puppet.features.stubs(:root?).returns(true)
Puppet::FileSystem.expects(:exist?).with(main_config_file_default_location).returns(false)
Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).never
@settings.send(:parse_config_files)
end
end
describe "when not root" do
it "should look for user config file default location if config settings haven't been overridden'" do
Puppet.features.stubs(:root?).returns(false)
seq = sequence "load config files"
Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq)
@settings.send(:parse_config_files)
end
end
end
describe "when parsing its configuration" do
before do
@settings = Puppet::Settings.new
@settings.stubs(:service_user_available?).returns true
@settings.stubs(:service_group_available?).returns true
@file = make_absolute("/some/file")
@userconfig = make_absolute("/test/userconfigfile")
@settings.define_settings :section, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" }
@settings.define_settings :section,
:config => { :type => :file, :default => @file, :desc => "eh" },
:one => { :default => "ONE", :desc => "a" },
:two => { :default => "$one TWO", :desc => "b" },
:three => { :default => "$one $two THREE", :desc => "c" }
@settings.stubs(:user_config_file).returns(@userconfig)
Puppet::FileSystem.stubs(:exist?).with(@file).returns true
Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false
end
it "should not ignore the report setting" do
@settings.define_settings :section, :report => { :default => "false", :desc => "a" }
# This is needed in order to make sure we pass on windows
myfile = File.expand_path(@file)
@settings[:config] = myfile
text = <<-CONF
[puppetd]
report=true
CONF
Puppet::FileSystem.expects(:exist?).with(myfile).returns(true)
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:report].should be_true
end
it "should use its current ':config' value for the file to parse" do
myfile = make_absolute("/my/file") # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it
@settings[:config] = myfile
Puppet::FileSystem.expects(:exist?).with(myfile).returns(true)
Puppet::FileSystem.expects(:read).with(myfile).returns "[main]"
@settings.send(:parse_config_files)
end
it "should not try to parse non-existent files" do
Puppet::FileSystem.expects(:exist?).with(@file).returns false
File.expects(:read).with(@file).never
@settings.send(:parse_config_files)
end
it "should return values set in the configuration file" do
text = "[main]
one = fileval
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "fileval"
end
#484 - this should probably be in the regression area
it "should not throw an exception on unknown parameters" do
text = "[main]\nnosuchparam = mval\n"
@settings.expects(:read_file).returns(text)
lambda { @settings.send(:parse_config_files) }.should_not raise_error
end
it "should convert booleans in the configuration file into Ruby booleans" do
text = "[main]
one = true
two = false
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == true
@settings[:two].should == false
end
it "should convert integers in the configuration file into Ruby Integers" do
text = "[main]
one = 65
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == 65
end
it "should support specifying all metadata (owner, group, mode) in the configuration file" do
@settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" }
otherfile = make_absolute("/other/file")
@settings.parse_config(<<-CONF)
[main]
myfile = #{otherfile} {owner = service, group = service, mode = 644}
CONF
@settings[:myfile].should == otherfile
@settings.metadata(:myfile).should == {:owner => "suser", :group => "sgroup", :mode => "644"}
end
it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do
@settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" }
otherfile = make_absolute("/other/file")
@settings.parse_config(<<-CONF)
[main]
myfile = #{otherfile} {owner = service}
CONF
@settings[:myfile].should == otherfile
@settings.metadata(:myfile).should == {:owner => "suser"}
end
it "should support loading metadata (owner, group, or mode) from a run_mode section in the configuration file" do
default_values = {}
PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key|
default_values[key] = 'default value'
end
@settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS
@settings.define_settings :master, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" }
otherfile = make_absolute("/other/file")
text = "[master]
myfile = #{otherfile} {mode = 664}
"
@settings.expects(:read_file).returns(text)
# will start initialization as user
@settings.preferred_run_mode.should == :user
@settings.send(:parse_config_files)
# change app run_mode to master
@settings.initialize_app_defaults(default_values.merge(:run_mode => :master))
@settings.preferred_run_mode.should == :master
# initializing the app should have reloaded the metadata based on run_mode
@settings[:myfile].should == otherfile
@settings.metadata(:myfile).should == {:mode => "664"}
end
it "does not use the metadata from the same setting in a different section" do
default_values = {}
PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key|
default_values[key] = 'default value'
end
file = make_absolute("/file")
default_mode = "0600"
@settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS
@settings.define_settings :master, :myfile => { :type => :file, :default => file, :desc => "a", :mode => default_mode }
text = "[master]
myfile = #{file}/foo
[agent]
myfile = #{file} {mode = 664}
"
@settings.expects(:read_file).returns(text)
# will start initialization as user
@settings.preferred_run_mode.should == :user
@settings.send(:parse_config_files)
# change app run_mode to master
@settings.initialize_app_defaults(default_values.merge(:run_mode => :master))
@settings.preferred_run_mode.should == :master
# initializing the app should have reloaded the metadata based on run_mode
@settings[:myfile].should == "#{file}/foo"
@settings.metadata(:myfile).should == { :mode => default_mode }
end
it "should call hooks associated with values set in the configuration file" do
values = []
@settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[main]
mysetting = setval
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
values.should == ["setval"]
end
it "should not call the same hook for values set multiple times in the configuration file" do
values = []
@settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[user]
mysetting = setval
[main]
mysetting = other
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
values.should == ["setval"]
end
it "should pass the environment-specific value to the hook when one is available" do
values = []
@settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
@settings.define_settings :section, :environment => { :default => "yay", :desc => "a" }
@settings.define_settings :section, :environments => { :default => "yay,foo", :desc => "a" }
text = "[main]
mysetting = setval
[yay]
mysetting = other
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
values.should == ["other"]
end
it "should pass the interpolated value to the hook when one is available" do
values = []
@settings.define_settings :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }}
@settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }}
text = "[main]
mysetting = $base/setval
"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
values.should == ["yay/setval"]
end
it "should allow hooks invoked at parse time to be deferred" do
hook_invoked = false
@settings.define_settings :section, :deferred => {:desc => '',
:hook => proc { |v| hook_invoked = true },
:call_hook => :on_initialize_and_write, }
@settings.define_settings(:main,
:logdir => { :type => :directory, :default => nil, :desc => "logdir" },
:confdir => { :type => :directory, :default => nil, :desc => "confdir" },
:vardir => { :type => :directory, :default => nil, :desc => "vardir" })
text = <<-EOD
[main]
deferred=$confdir/goose
EOD
@settings.stubs(:read_file).returns(text)
@settings.initialize_global_settings
hook_invoked.should be_false
@settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir')
hook_invoked.should be_true
@settings[:deferred].should eq(File.expand_path('/path/to/confdir/goose'))
end
it "does not require the value for a setting without a hook to resolve during global setup" do
hook_invoked = false
@settings.define_settings :section, :can_cause_problems => {:desc => '' }
@settings.define_settings(:main,
:logdir => { :type => :directory, :default => nil, :desc => "logdir" },
:confdir => { :type => :directory, :default => nil, :desc => "confdir" },
:vardir => { :type => :directory, :default => nil, :desc => "vardir" })
text = <<-EOD
[main]
can_cause_problems=$confdir/goose
EOD
@settings.stubs(:read_file).returns(text)
@settings.initialize_global_settings
@settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir')
@settings[:can_cause_problems].should eq(File.expand_path('/path/to/confdir/goose'))
end
it "should allow empty values" do
@settings.define_settings :section, :myarg => { :default => "myfile", :desc => "a" }
text = "[main]
myarg =
"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:myarg].should == ""
end
describe "deprecations" do
let(:settings) { Puppet::Settings.new }
let(:app_defaults) {
{
:logdir => "/dev/null",
:confdir => "/dev/null",
:vardir => "/dev/null",
}
}
def assert_accessing_setting_is_deprecated(settings, setting)
Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated. See http://links.puppetlabs.com/env-settings-deprecations")
settings[setting.intern] = apath = File.expand_path('foo')
expect(settings[setting.intern]).to eq(apath)
end
before(:each) do
settings.define_settings(:main, {
:logdir => { :default => 'a', :desc => 'a' },
:confdir => { :default => 'b', :desc => 'b' },
:vardir => { :default => 'c', :desc => 'c' },
})
end
context "complete" do
let(:completely_deprecated_settings) do
settings.define_settings(:main, {
:manifestdir => {
:default => 'foo',
:desc => 'a deprecated setting',
:deprecated => :completely,
}
})
settings
end
it "warns when set in puppet.conf" do
Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir')
completely_deprecated_settings.parse_config(<<-CONF)
manifestdir='should warn'
CONF
completely_deprecated_settings.initialize_app_defaults(app_defaults)
end
it "warns when set on the commandline" do
Puppet.expects(:deprecation_warning).with(regexp_matches(/manifestdir is deprecated\./), 'setting-manifestdir')
args = ["--manifestdir", "/some/value"]
completely_deprecated_settings.send(:parse_global_options, args)
completely_deprecated_settings.initialize_app_defaults(app_defaults)
end
it "warns when set in code" do
assert_accessing_setting_is_deprecated(completely_deprecated_settings, 'manifestdir')
end
end
context "partial" do
let(:partially_deprecated_settings) do
settings.define_settings(:main, {
:modulepath => {
:default => 'foo',
:desc => 'a partially deprecated setting',
:deprecated => :allowed_on_commandline,
}
})
settings
end
it "warns for a deprecated setting allowed on the command line set in puppet.conf" do
Puppet.expects(:deprecation_warning).with(regexp_matches(/modulepath is deprecated in puppet\.conf/), 'puppet-conf-setting-modulepath')
partially_deprecated_settings.parse_config(<<-CONF)
modulepath='should warn'
CONF
partially_deprecated_settings.initialize_app_defaults(app_defaults)
end
it "does not warn when manifest is set on command line" do
Puppet.expects(:deprecation_warning).never
args = ["--modulepath", "/some/value"]
partially_deprecated_settings.send(:parse_global_options, args)
partially_deprecated_settings.initialize_app_defaults(app_defaults)
end
it "warns when set in code" do
assert_accessing_setting_is_deprecated(partially_deprecated_settings, 'modulepath')
end
end
end
end
describe "when there are multiple config files" do
let(:main_config_text) { "[main]\none = main\ntwo = main2" }
let(:user_config_text) { "[main]\none = user\n" }
let(:seq) { sequence "config_file_sequence" }
before :each do
@settings = Puppet::Settings.new
@settings.define_settings(:section,
{ :confdir => { :default => nil, :desc => "Conf dir" },
:config => { :default => "$confdir/puppet.conf", :desc => "Config" },
:one => { :default => "ONE", :desc => "a" },
:two => { :default => "TWO", :desc => "b" }, })
end
context "running non-root without explicit config file" do
before :each do
Puppet.features.stubs(:root?).returns(false)
Puppet::FileSystem.expects(:exist?).
with(user_config_file_default_location).
returns(true).in_sequence(seq)
@settings.expects(:read_file).
with(user_config_file_default_location).
returns(user_config_text).in_sequence(seq)
end
it "should return values from the user config file" do
@settings.send(:parse_config_files)
@settings[:one].should == "user"
end
it "should not return values from the main config file" do
@settings.send(:parse_config_files)
@settings[:two].should == "TWO"
end
end
context "running as root without explicit config file" do
before :each do
Puppet.features.stubs(:root?).returns(true)
Puppet::FileSystem.expects(:exist?).
with(main_config_file_default_location).
returns(true).in_sequence(seq)
@settings.expects(:read_file).
with(main_config_file_default_location).
returns(main_config_text).in_sequence(seq)
end
it "should return values from the main config file" do
@settings.send(:parse_config_files)
@settings[:one].should == "main"
end
it "should not return values from the user config file" do
@settings.send(:parse_config_files)
@settings[:two].should == "main2"
end
end
context "running with an explicit config file as a user (e.g. Apache + Passenger)" do
before :each do
Puppet.features.stubs(:root?).returns(false)
@settings[:confdir] = File.dirname(main_config_file_default_location)
Puppet::FileSystem.expects(:exist?).
with(main_config_file_default_location).
returns(true).in_sequence(seq)
@settings.expects(:read_file).
with(main_config_file_default_location).
returns(main_config_text).in_sequence(seq)
end
it "should return values from the main config file" do
@settings.send(:parse_config_files)
@settings[:one].should == "main"
end
it "should not return values from the user config file" do
@settings.send(:parse_config_files)
@settings[:two].should == "main2"
end
end
end
describe "when reparsing its configuration" do
before do
@file = make_absolute("/test/file")
@userconfig = make_absolute("/test/userconfigfile")
@settings = Puppet::Settings.new
@settings.define_settings :section,
:config => { :type => :file, :default => @file, :desc => "a" },
:one => { :default => "ONE", :desc => "a" },
:two => { :default => "$one TWO", :desc => "b" },
:three => { :default => "$one $two THREE", :desc => "c" }
Puppet::FileSystem.stubs(:exist?).with(@file).returns true
Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false
@settings.stubs(:user_config_file).returns(@userconfig)
end
it "does not create the WatchedFile instance and should not parse if the file does not exist" do
Puppet::FileSystem.expects(:exist?).with(@file).returns false
Puppet::Util::WatchedFile.expects(:new).never
@settings.expects(:parse_config_files).never
@settings.reparse_config_files
end
context "and watched file exists" do
before do
@watched_file = Puppet::Util::WatchedFile.new(@file)
Puppet::Util::WatchedFile.expects(:new).with(@file).returns @watched_file
end
it "uses a WatchedFile instance to determine if the file has changed" do
@watched_file.expects(:changed?)
@settings.reparse_config_files
end
it "does not reparse if the file has not changed" do
@watched_file.expects(:changed?).returns false
@settings.expects(:parse_config_files).never
@settings.reparse_config_files
end
it "reparses if the file has changed" do
@watched_file.expects(:changed?).returns true
@settings.expects(:parse_config_files)
@settings.reparse_config_files
end
it "replaces in-memory values with on-file values" do
@watched_file.stubs(:changed?).returns(true)
@settings[:one] = "init"
# Now replace the value
text = "[main]\none = disk-replace\n"
@settings.stubs(:read_file).returns(text)
@settings.reparse_config_files
@settings[:one].should == "disk-replace"
end
end
it "should retain parameters set by cli when configuration files are reparsed" do
@settings.handlearg("--one", "clival")
text = "[main]\none = on-disk\n"
@settings.stubs(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "clival"
end
it "should remove in-memory values that are no longer set in the file" do
# Init the value
text = "[main]\none = disk-init\n"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "disk-init"
# Now replace the value
text = "[main]\ntwo = disk-replace\n"
@settings.expects(:read_file).returns(text)
@settings.send(:parse_config_files)
# The originally-overridden value should be replaced with the default
@settings[:one].should == "ONE"
# and we should now have the new value in memory
@settings[:two].should == "disk-replace"
end
it "should retain in-memory values if the file has a syntax error" do
# Init the value
text = "[main]\none = initial-value\n"
@settings.expects(:read_file).with(@file).returns(text)
@settings.send(:parse_config_files)
@settings[:one].should == "initial-value"
# Now replace the value with something bogus
text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n"
@settings.expects(:read_file).with(@file).returns(text)
@settings.send(:parse_config_files)
# The originally-overridden value should not be replaced with the default
@settings[:one].should == "initial-value"
# and we should not have the new value in memory
@settings[:kenny].should be_nil
end
end
it "should provide a method for creating a catalog of resources from its configuration" do
Puppet::Settings.new.should respond_to(:to_catalog)
end
describe "when creating a catalog" do
before do
@settings = Puppet::Settings.new
@settings.stubs(:service_user_available?).returns true
@prefix = Puppet.features.posix? ? "" : "C:"
end
it "should add all file resources to the catalog if no sections have been specified" do
@settings.define_settings :main,
:maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a"},
:seconddir => { :type => :directory, :default => @prefix+"/seconddir", :desc => "a"}
@settings.define_settings :other,
:otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" }
catalog = @settings.to_catalog
[@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path|
catalog.resource(:file, path).should be_instance_of(Puppet::Resource)
end
end
it "should add only files in the specified sections if section names are provided" do
@settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" }
@settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" }
catalog = @settings.to_catalog(:main)
catalog.resource(:file, @prefix+"/otherdir").should be_nil
catalog.resource(:file, @prefix+"/maindir").should be_instance_of(Puppet::Resource)
end
it "should not try to add the same file twice" do
@settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" }
@settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" }
lambda { @settings.to_catalog }.should_not raise_error
end
it "should ignore files whose :to_resource method returns nil" do
@settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" }
@settings.setting(:maindir).expects(:to_resource).returns nil
Puppet::Resource::Catalog.any_instance.expects(:add_resource).never
@settings.to_catalog
end
+ it "should ignore manifestdir if environmentpath is set" do
+ @settings.define_settings :main,
+ :manifestdir => { :type => :directory, :default => @prefix+"/manifestdir", :desc => "a" },
+ :environmentpath => { :type => :path, :default => @prefix+"/envs", :desc => "a" }
+
+ catalog = @settings.to_catalog(:main)
+
+ expect(catalog).to_not have_resource("File[#{@prefix}/manifestdir]")
+ end
+
describe "on Microsoft Windows" do
before :each do
Puppet.features.stubs(:root?).returns true
Puppet.features.stubs(:microsoft_windows?).returns true
@settings.define_settings :foo,
:mkusers => { :type => :boolean, :default => true, :desc => "e" },
:user => { :default => "suser", :desc => "doc" },
:group => { :default => "sgroup", :desc => "doc" }
@settings.define_settings :other,
:otherdir => { :type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"}
@catalog = @settings.to_catalog
end
it "it should not add users and groups to the catalog" do
@catalog.resource(:user, "suser").should be_nil
@catalog.resource(:group, "sgroup").should be_nil
end
end
describe "adding default directory environment to the catalog" do
let(:tmpenv) { tmpdir("envs") }
let(:default_path) { "#{tmpenv}/environments" }
before(:each) do
@settings.define_settings :main,
:environment => { :default => "production", :desc => "env"},
:environmentpath => { :type => :path, :default => default_path, :desc => "envpath"}
end
it "adds if environmentpath exists" do
envpath = "#{tmpenv}/custom_envpath"
@settings[:environmentpath] = envpath
Dir.mkdir(envpath)
catalog = @settings.to_catalog
expect(catalog.resource_keys).to include(["File", "#{envpath}/production"])
end
it "adds the first directory of environmentpath" do
envdir = "#{tmpenv}/custom_envpath"
envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir"
@settings[:environmentpath] = envpath
Dir.mkdir(envdir)
catalog = @settings.to_catalog
expect(catalog.resource_keys).to include(["File", "#{envdir}/production"])
end
it "handles a non-existent environmentpath" do
catalog = @settings.to_catalog
expect(catalog.resource_keys).to be_empty
end
it "handles a default environmentpath" do
Dir.mkdir(default_path)
catalog = @settings.to_catalog
expect(catalog.resource_keys).to include(["File", "#{default_path}/production"])
end
+
+ it "does not add if the path to the default directory environment exists as a symlink", :if => Puppet.features.manages_symlinks? do
+ Dir.mkdir(default_path)
+ Puppet::FileSystem.symlink("#{tmpenv}/nowhere", File.join(default_path, 'production'))
+ catalog = @settings.to_catalog
+ expect(catalog.resource_keys).to_not include(["File", "#{default_path}/production"])
+ end
end
describe "when adding users and groups to the catalog" do
before do
Puppet.features.stubs(:root?).returns true
Puppet.features.stubs(:microsoft_windows?).returns false
@settings.define_settings :foo,
:mkusers => { :type => :boolean, :default => true, :desc => "e" },
:user => { :default => "suser", :desc => "doc" },
:group => { :default => "sgroup", :desc => "doc" }
@settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"}
@catalog = @settings.to_catalog
end
it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do
@catalog.resource(:user, "suser").should be_instance_of(Puppet::Resource)
@catalog.resource(:group, "sgroup").should be_instance_of(Puppet::Resource)
end
it "should only add users and groups to the catalog from specified sections" do
@settings.define_settings :yay, :yaydir => { :type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"}
catalog = @settings.to_catalog(:other)
catalog.resource(:user, "jane").should be_nil
catalog.resource(:group, "billy").should be_nil
end
it "should not add users or groups to the catalog if :mkusers not running as root" do
Puppet.features.stubs(:root?).returns false
catalog = @settings.to_catalog
catalog.resource(:user, "suser").should be_nil
catalog.resource(:group, "sgroup").should be_nil
end
it "should not add users or groups to the catalog if :mkusers is not a valid setting" do
Puppet.features.stubs(:root?).returns true
settings = Puppet::Settings.new
settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"}
catalog = settings.to_catalog
catalog.resource(:user, "suser").should be_nil
catalog.resource(:group, "sgroup").should be_nil
end
it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do
@settings[:mkusers] = false
catalog = @settings.to_catalog
catalog.resource(:user, "suser").should be_nil
catalog.resource(:group, "sgroup").should be_nil
end
it "should not try to add users or groups to the catalog twice" do
@settings.define_settings :yay, :yaydir => {:type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"}
# This would fail if users/groups were added twice
lambda { @settings.to_catalog }.should_not raise_error
end
it "should set :ensure to :present on each created user and group" do
@catalog.resource(:user, "suser")[:ensure].should == :present
@catalog.resource(:group, "sgroup")[:ensure].should == :present
end
it "should set each created user's :gid to the service group" do
@settings.to_catalog.resource(:user, "suser")[:gid].should == "sgroup"
end
it "should not attempt to manage the root user" do
Puppet.features.stubs(:root?).returns true
@settings.define_settings :foo, :foodir => {:type => :directory, :default => "/foodir", :desc => "a", :owner => "root", :group => "service"}
@settings.to_catalog.resource(:user, "root").should be_nil
end
end
end
it "should be able to be converted to a manifest" do
Puppet::Settings.new.should respond_to(:to_manifest)
end
describe "when being converted to a manifest" do
it "should produce a string with the code for each resource joined by two carriage returns" do
@settings = Puppet::Settings.new
@settings.define_settings :main,
:maindir => { :type => :directory, :default => "/maindir", :desc => "a"},
:seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"}
main = stub 'main_resource', :ref => "File[/maindir]"
main.expects(:to_manifest).returns "maindir"
second = stub 'second_resource', :ref => "File[/seconddir]"
second.expects(:to_manifest).returns "seconddir"
@settings.setting(:maindir).expects(:to_resource).returns main
@settings.setting(:seconddir).expects(:to_resource).returns second
@settings.to_manifest.split("\n\n").sort.should == %w{maindir seconddir}
end
end
describe "when using sections of the configuration to manage the local host" do
before do
@settings = Puppet::Settings.new
@settings.stubs(:service_user_available?).returns true
@settings.stubs(:service_group_available?).returns true
@settings.define_settings :main, :noop => { :default => false, :desc => "", :type => :boolean }
@settings.define_settings :main,
:maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" },
:seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"}
@settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" }
@settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => '0755'}
@settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"}
@settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => '0755'}
end
it "should provide a method that creates directories with the correct modes" do
Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields
Dir.expects(:mkdir).with(make_absolute("/otherdir"), '0755')
@settings.mkdir(:otherdir)
end
it "should create a catalog with the specified sections" do
@settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo")
@settings.use(:main, :other)
end
it "should canonicalize the sections" do
@settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo")
@settings.use("main", "other")
end
it "should ignore sections that have already been used" do
@settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo")
@settings.use(:main)
@settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo")
@settings.use(:main, :other)
end
it "should convert the created catalog to a RAL catalog" do
@catalog = Puppet::Resource::Catalog.new("foo")
@settings.expects(:to_catalog).with(:main).returns @catalog
@catalog.expects(:to_ral).returns @catalog
@settings.use(:main)
end
it "should specify that it is not managing a host catalog" do
catalog = Puppet::Resource::Catalog.new("foo")
catalog.expects(:apply)
@settings.expects(:to_catalog).returns catalog
catalog.stubs(:to_ral).returns catalog
catalog.expects(:host_config=).with false
@settings.use(:main)
end
it "should support a method for re-using all currently used sections" do
@settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo")
@settings.use(:main, :third)
@settings.reuse
end
it "should fail with an appropriate message if any resources fail" do
@catalog = Puppet::Resource::Catalog.new("foo")
@catalog.stubs(:to_ral).returns @catalog
@settings.expects(:to_catalog).returns @catalog
@trans = mock("transaction")
@catalog.expects(:apply).yields(@trans)
@trans.expects(:any_failed?).returns(true)
resource = Puppet::Type.type(:notify).new(:title => 'failed')
status = Puppet::Resource::Status.new(resource)
event = Puppet::Transaction::Event.new(
:name => 'failure',
:status => 'failure',
:message => 'My failure')
status.add_event(event)
report = Puppet::Transaction::Report.new('apply')
report.add_resource_status(status)
@trans.expects(:report).returns report
@settings.expects(:raise).with(includes("My failure"))
@settings.use(:whatever)
end
end
describe "when dealing with printing configs" do
before do
@settings = Puppet::Settings.new
#these are the magic default values
@settings.stubs(:value).with(:configprint).returns("")
@settings.stubs(:value).with(:genconfig).returns(false)
@settings.stubs(:value).with(:genmanifest).returns(false)
@settings.stubs(:value).with(:environment).returns(nil)
end
describe "when checking print_config?" do
it "should return false when the :configprint, :genconfig and :genmanifest are not set" do
@settings.print_configs?.should be_false
end
it "should return true when :configprint has a value" do
@settings.stubs(:value).with(:configprint).returns("something")
@settings.print_configs?.should be_true
end
it "should return true when :genconfig has a value" do
@settings.stubs(:value).with(:genconfig).returns(true)
@settings.print_configs?.should be_true
end
it "should return true when :genmanifest has a value" do
@settings.stubs(:value).with(:genmanifest).returns(true)
@settings.print_configs?.should be_true
end
end
describe "when printing configs" do
describe "when :configprint has a value" do
it "should call print_config_options" do
@settings.stubs(:value).with(:configprint).returns("something")
@settings.expects(:print_config_options)
@settings.print_configs
end
it "should get the value of the option using the environment" do
@settings.stubs(:value).with(:configprint).returns("something")
@settings.stubs(:include?).with("something").returns(true)
@settings.expects(:value).with(:environment).returns("env")
@settings.expects(:value).with("something", "env").returns("foo")
@settings.stubs(:puts).with("foo")
@settings.print_configs
end
it "should print the value of the option" do
@settings.stubs(:value).with(:configprint).returns("something")
@settings.stubs(:include?).with("something").returns(true)
@settings.stubs(:value).with("something", nil).returns("foo")
@settings.expects(:puts).with("foo")
@settings.print_configs
end
it "should print the value pairs if there are multiple options" do
@settings.stubs(:value).with(:configprint).returns("bar,baz")
@settings.stubs(:include?).with("bar").returns(true)
@settings.stubs(:include?).with("baz").returns(true)
@settings.stubs(:value).with("bar", nil).returns("foo")
@settings.stubs(:value).with("baz", nil).returns("fud")
@settings.expects(:puts).with("bar = foo")
@settings.expects(:puts).with("baz = fud")
@settings.print_configs
end
it "should return true after printing" do
@settings.stubs(:value).with(:configprint).returns("something")
@settings.stubs(:include?).with("something").returns(true)
@settings.stubs(:value).with("something", nil).returns("foo")
@settings.stubs(:puts).with("foo")
@settings.print_configs.should be_true
end
it "should return false if a config param is not found" do
@settings.stubs :puts
@settings.stubs(:value).with(:configprint).returns("something")
@settings.stubs(:include?).with("something").returns(false)
@settings.print_configs.should be_false
end
end
describe "when genconfig is true" do
before do
@settings.stubs :puts
end
it "should call to_config" do
@settings.stubs(:value).with(:genconfig).returns(true)
@settings.expects(:to_config)
@settings.print_configs
end
it "should return true from print_configs" do
@settings.stubs(:value).with(:genconfig).returns(true)
@settings.stubs(:to_config)
@settings.print_configs.should be_true
end
end
describe "when genmanifest is true" do
before do
@settings.stubs :puts
end
it "should call to_config" do
@settings.stubs(:value).with(:genmanifest).returns(true)
@settings.expects(:to_manifest)
@settings.print_configs
end
it "should return true from print_configs" do
@settings.stubs(:value).with(:genmanifest).returns(true)
@settings.stubs(:to_manifest)
@settings.print_configs.should be_true
end
end
end
end
describe "when determining if the service user is available" do
let(:settings) do
settings = Puppet::Settings.new
settings.define_settings :main, :user => { :default => nil, :desc => "doc" }
settings
end
def a_user_type_for(username)
user = mock 'user'
Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == username }.returns user
user
end
it "should return false if there is no user setting" do
settings.should_not be_service_user_available
end
it "should return false if the user provider says the user is missing" do
settings[:user] = "foo"
a_user_type_for("foo").expects(:exists?).returns false
settings.should_not be_service_user_available
end
it "should return true if the user provider says the user is present" do
settings[:user] = "foo"
a_user_type_for("foo").expects(:exists?).returns true
settings.should be_service_user_available
end
it "caches the result of determining if the user is present" do
settings[:user] = "foo"
a_user_type_for("foo").expects(:exists?).returns true
settings.should be_service_user_available
settings.should be_service_user_available
end
end
describe "when determining if the service group is available" do
let(:settings) do
settings = Puppet::Settings.new
settings.define_settings :main, :group => { :default => nil, :desc => "doc" }
settings
end
def a_group_type_for(groupname)
group = mock 'group'
Puppet::Type.type(:group).expects(:new).with { |args| args[:name] == groupname }.returns group
group
end
it "should return false if there is no group setting" do
settings.should_not be_service_group_available
end
it "should return false if the group provider says the group is missing" do
settings[:group] = "foo"
a_group_type_for("foo").expects(:exists?).returns false
settings.should_not be_service_group_available
end
it "should return true if the group provider says the group is present" do
settings[:group] = "foo"
a_group_type_for("foo").expects(:exists?).returns true
settings.should be_service_group_available
end
it "caches the result of determining if the group is present" do
settings[:group] = "foo"
a_group_type_for("foo").expects(:exists?).returns true
settings.should be_service_group_available
settings.should be_service_group_available
end
end
describe "when dealing with command-line options" do
let(:settings) { Puppet::Settings.new }
it "should get options from Puppet.settings.optparse_addargs" do
settings.expects(:optparse_addargs).returns([])
settings.send(:parse_global_options, [])
end
it "should add options to OptionParser" do
settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]])
settings.expects(:handlearg).with("--option", true)
settings.send(:parse_global_options, ["--option"])
end
it "should not die if it sees an unrecognized option, because the app/face may handle it later" do
expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error
end
it "should not pass an unrecognized option to handleargs" do
settings.expects(:handlearg).with("--topuppet", "value").never
expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error
end
it "should pass valid puppet settings options to handlearg even if they appear after an unrecognized option" do
settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]])
settings.expects(:handlearg).with("--option", true)
settings.send(:parse_global_options, ["--invalidoption", "--option"])
end
it "should transform boolean option to normal form" do
Puppet::Settings.clean_opt("--[no-]option", true).should == ["--option", true]
end
it "should transform boolean option to no- form" do
Puppet::Settings.clean_opt("--[no-]option", false).should == ["--no-option", false]
end
it "should set preferred run mode from --run_mode string without error" do
args = ["--run_mode", "master"]
settings.expects(:handlearg).with("--run_mode", "master").never
expect { settings.send(:parse_global_options, args) } .to_not raise_error
Puppet.settings.preferred_run_mode.should == :master
args.empty?.should == true
end
it "should set preferred run mode from --run_mode= string without error" do
args = ["--run_mode=master"]
settings.expects(:handlearg).with("--run_mode", "master").never
expect { settings.send(:parse_global_options, args) } .to_not raise_error
Puppet.settings.preferred_run_mode.should == :master
args.empty?.should == true
end
end
describe "default_certname" do
describe "using hostname and domainname" do
before :each do
Puppet::Settings.stubs(:hostname_fact).returns("testhostname")
Puppet::Settings.stubs(:domain_fact).returns("domain.test.")
end
it "should use both to generate fqdn" do
Puppet::Settings.default_certname.should =~ /testhostname\.domain\.test/
end
it "should remove trailing dots from fqdn" do
Puppet::Settings.default_certname.should == 'testhostname.domain.test'
end
end
describe "using just hostname" do
before :each do
Puppet::Settings.stubs(:hostname_fact).returns("testhostname")
Puppet::Settings.stubs(:domain_fact).returns("")
end
it "should use only hostname to generate fqdn" do
Puppet::Settings.default_certname.should == "testhostname"
end
it "should removing trailing dots from fqdn" do
Puppet::Settings.default_certname.should == "testhostname"
end
end
end
end
diff --git a/spec/unit/ssl/validator_spec.rb b/spec/unit/ssl/validator_spec.rb
index ade1575dc..9faec6813 100644
--- a/spec/unit/ssl/validator_spec.rb
+++ b/spec/unit/ssl/validator_spec.rb
@@ -1,353 +1,419 @@
require 'spec_helper'
require 'puppet/ssl'
describe Puppet::SSL::Validator::DefaultValidator do
let(:ssl_context) do
mock('OpenSSL::X509::StoreContext')
end
let(:ssl_configuration) do
Puppet::SSL::Configuration.new(
Puppet[:localcacert],
:ca_chain_file => Puppet[:ssl_client_ca_chain],
:ca_auth_file => Puppet[:ssl_client_ca_auth])
end
let(:ssl_host) do
stub('ssl_host',
:ssl_store => nil,
:certificate => stub('cert', :content => nil),
:key => stub('key', :content => nil))
end
subject do
described_class.new(ssl_configuration,
ssl_host)
end
before :each do
ssl_configuration.stubs(:read_file).
with(Puppet[:localcacert]).
returns(root_ca)
end
describe '#call' do
before :each do
ssl_context.stubs(:current_cert).returns(*cert_chain_in_callback_order)
ssl_context.stubs(:chain).returns(cert_chain)
end
context 'When pre-verification is not OK' do
context 'and the ssl_context is in an error state' do
- before :each do
- ssl_context.stubs(:error_string).returns("Something went wrong.")
+ let(:root_subject) { OpenSSL::X509::Certificate.new(root_ca).subject.to_s }
+ let(:code) { OpenSSL::X509::V_ERR_INVALID_CA }
+
+ it 'rejects the connection' do
+ ssl_context.stubs(:error_string).returns("Something went wrong")
+ ssl_context.stubs(:error).returns(code)
+
+ expect(subject.call(false, ssl_context)).to eq(false)
end
it 'makes the error available via #verify_errors' do
+ ssl_context.stubs(:error_string).returns("Something went wrong")
+ ssl_context.stubs(:error).returns(code)
+
+ subject.call(false, ssl_context)
+ expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"])
+ end
+
+ it 'uses a generic message if error_string is nil' do
+ ssl_context.stubs(:error_string).returns(nil)
+ ssl_context.stubs(:error).returns(code)
+
subject.call(false, ssl_context)
- msg_suffix = OpenSSL::X509::Certificate.new(root_ca).subject
- subject.verify_errors.should == ["Something went wrong. for #{msg_suffix}"]
+ expect(subject.verify_errors).to eq(["OpenSSL error #{code} for #{root_subject}"])
+ end
+
+ it 'uses 0 for nil error codes' do
+ ssl_context.stubs(:error_string).returns("Something went wrong")
+ ssl_context.stubs(:error).returns(nil)
+
+ subject.call(false, ssl_context)
+ expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"])
+ end
+
+ context "when CRL is not yet valid" do
+ before :each do
+ ssl_context.stubs(:error_string).returns("CRL is not yet valid")
+ ssl_context.stubs(:error).returns(OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID)
+ end
+
+ it 'rejects nil CRL' do
+ ssl_context.stubs(:current_crl).returns(nil)
+
+ expect(subject.call(false, ssl_context)).to eq(false)
+ expect(subject.verify_errors).to eq(["CRL is not yet valid"])
+ end
+
+ it 'includes the CRL issuer in the verify error message' do
+ crl = OpenSSL::X509::CRL.new
+ crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']])
+ crl.last_update = Time.now + 24 * 60 * 60
+ ssl_context.stubs(:current_crl).returns(crl)
+
+ subject.call(false, ssl_context)
+ expect(subject.verify_errors).to eq(["CRL is not yet valid for /CN=Puppet CA: puppetmaster.example.com"])
+ end
+
+ it 'rejects CRLs whose last_update time is more than 5 minutes in the future' do
+ crl = OpenSSL::X509::CRL.new
+ crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']])
+ crl.last_update = Time.now + 24 * 60 * 60
+ ssl_context.stubs(:current_crl).returns(crl)
+
+ expect(subject.call(false, ssl_context)).to eq(false)
+ end
+
+ it 'accepts CRLs whose last_update time is 10 seconds in the future' do
+ crl = OpenSSL::X509::CRL.new
+ crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']])
+ crl.last_update = Time.now + 10
+ ssl_context.stubs(:current_crl).returns(crl)
+
+ expect(subject.call(false, ssl_context)).to eq(true)
+ end
end
end
end
context 'When pre-verification is OK' do
context 'and the ssl_context is in an error state' do
before :each do
- ssl_context.stubs(:error_string).returns("Something went wrong.")
+ ssl_context.stubs(:error_string).returns("Something went wrong")
end
it 'does not make the error available via #verify_errors' do
subject.call(true, ssl_context)
subject.verify_errors.should == []
end
end
context 'and the chain is valid' do
it 'is true for each CA certificate in the chain' do
(cert_chain.length - 1).times do
subject.call(true, ssl_context).should be_true
end
end
it 'is true for the SSL certificate ending the chain' do
(cert_chain.length - 1).times do
subject.call(true, ssl_context)
end
subject.call(true, ssl_context).should be_true
end
end
context 'and the chain is invalid' do
before :each do
ssl_configuration.stubs(:read_file).
with(Puppet[:localcacert]).
returns(agent_ca)
end
it 'is true for each CA certificate in the chain' do
(cert_chain.length - 1).times do
subject.call(true, ssl_context).should be_true
end
end
it 'is false for the SSL certificate ending the chain' do
(cert_chain.length - 1).times do
subject.call(true, ssl_context)
end
subject.call(true, ssl_context).should be_false
end
end
context 'an error is raised inside of #call' do
before :each do
ssl_context.expects(:current_cert).raises(StandardError, "BOOM!")
end
it 'is false' do
subject.call(true, ssl_context).should be_false
end
it 'makes the error available through #verify_errors' do
subject.call(true, ssl_context)
subject.verify_errors.should == ["BOOM!"]
end
end
end
end
describe '#setup_connection' do
it 'updates the connection for verification' do
subject.stubs(:ssl_certificates_are_present?).returns(true)
connection = mock('Net::HTTP')
connection.expects(:cert_store=).with(ssl_host.ssl_store)
connection.expects(:ca_file=).with(ssl_configuration.ca_auth_file)
connection.expects(:cert=).with(ssl_host.certificate.content)
connection.expects(:key=).with(ssl_host.key.content)
connection.expects(:verify_callback=).with(subject)
connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
subject.setup_connection(connection)
end
it 'does not perform verification if certificate files are missing' do
subject.stubs(:ssl_certificates_are_present?).returns(false)
connection = mock('Net::HTTP')
connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
subject.setup_connection(connection)
end
end
describe '#valid_peer?' do
before :each do
peer_certs = cert_chain_in_callback_order.map do |c|
Puppet::SSL::Certificate.from_instance(c)
end
subject.instance_variable_set(:@peer_certs, peer_certs)
end
context 'when the peer presents a valid chain' do
before :each do
subject.stubs(:has_authz_peer_cert).returns(true)
end
it 'is true' do
subject.valid_peer?.should be_true
end
end
context 'when the peer presents an invalid chain' do
before :each do
subject.stubs(:has_authz_peer_cert).returns(false)
end
it 'is false' do
subject.valid_peer?.should be_false
end
it 'makes a helpful error message available via #verify_errors' do
subject.valid_peer?
subject.verify_errors.should == [expected_authz_error_msg]
end
end
end
describe '#has_authz_peer_cert' do
context 'when the Root CA is listed as authorized' do
it 'returns true when the SSL cert is issued by the Master CA' do
subject.has_authz_peer_cert(cert_chain, [root_ca_cert]).should be_true
end
it 'returns true when the SSL cert is issued by the Agent CA' do
subject.has_authz_peer_cert(cert_chain_agent_ca, [root_ca_cert]).should be_true
end
end
context 'when the Master CA is listed as authorized' do
it 'returns false when the SSL cert is issued by the Master CA' do
subject.has_authz_peer_cert(cert_chain, [master_ca_cert]).should be_true
end
it 'returns true when the SSL cert is issued by the Agent CA' do
subject.has_authz_peer_cert(cert_chain_agent_ca, [master_ca_cert]).should be_false
end
end
context 'when the Agent CA is listed as authorized' do
it 'returns true when the SSL cert is issued by the Master CA' do
subject.has_authz_peer_cert(cert_chain, [agent_ca_cert]).should be_false
end
it 'returns true when the SSL cert is issued by the Agent CA' do
subject.has_authz_peer_cert(cert_chain_agent_ca, [agent_ca_cert]).should be_true
end
end
end
def root_ca
<<-ROOT_CA
-----BEGIN CERTIFICATE-----
MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV
BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK
DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0
OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA
S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i
a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj
UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw
TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA
A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q
rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7
rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R
-----END CERTIFICATE-----
ROOT_CA
end
def master_ca
<<-MASTER_CA
-----BEGIN CERTIFICATE-----
MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi
BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ
ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa
MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI
AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ
srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO
Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL
DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ
ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz
BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U
l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu
-----END CERTIFICATE-----
MASTER_CA
end
def agent_ca
<<-AGENT_CA
-----BEGIN CERTIFICATE-----
MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290
IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs
ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh
BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB
FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow
GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU
jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41
ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM
EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA
t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN
AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls
ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI
0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE=
-----END CERTIFICATE-----
AGENT_CA
end
# Signed by the master CA (Good)
def master_issued_by_master_ca
<<-GOOD_SSL_CERT
-----BEGIN CERTIFICATE-----
MIICZzCCAhGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl
cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh
bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl
cnZlciBPcGVyYXRpb25zMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0OFow
HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUA
A0sAMEgCQQDACW8fryVZH0dC7vYUASonVBKYcILnKN2O9QX7RenZGN1TWek9LQxr
yQFDyp7WJ8jUw6nENGniLU8J+QSSxryjAgMBAAGjgdkwgdYwWwYDVR0jBFQwUqFN
pEswSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv
bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEOCAQIwDAYDVR0TAQH/BAIwADAL
BgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD0GA1Ud
EQQ2MDSCE21hc3RlcjEuZXhhbXBsZS5vcmeCB21hc3RlcjGCBnB1cHBldIIMcHVw
cGV0bWFzdGVyMA0GCSqGSIb3DQEBBQUAA0EAo8PvgLrah6jQVs6YCBxOTn13PDip
fVbcRsFd0dtIr00N61bCqr6Fa0aRwy424gh6bVJTNmk2zoaH7r025dZRhw==
-----END CERTIFICATE-----
GOOD_SSL_CERT
end
# Signed by the agent CA, not the master CA (Rogue)
def master_issued_by_agent_ca
<<-BAD_SSL_CERT
-----BEGIN CERTIFICATE-----
MIICZjCCAhCgAwIBAgIBBDANBgkqhkiG9w0BAQUFADB9MSMwIQYDVQQDExpJbnRl
cm1lZGlhdGUgQ0EgKGFnZW50LWNhKTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt
cGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEaMBgGA1UECxMRU2Vy
dmVyIE9wZXJhdGlvbnMwHhcNMTMwMzMwMDU1MDQ4WhcNMzMwMzI1MDU1MDQ4WjAe
MRwwGgYDVQQDDBNtYXN0ZXIxLmV4YW1wbGUub3JnMFwwDQYJKoZIhvcNAQEBBQAD
SwAwSAJBAPnCDnryLLXWepGLqsdBWlytfeakE/yijM8GlE/yT0SbpJInIhJR1N1A
0RskriHrxTU5qQEhd0RIja7K5o4NYksCAwEAAaOB2TCB1jBbBgNVHSMEVDBSoU2k
SzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9u
czEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBATAMBgNVHRMBAf8EAjAAMAsG
A1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0R
BDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVwcGV0ggxwdXBw
ZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADQQA841IzHLlnn4RIJ0/BOZ/16iWC1dNr
jV9bELC5OxeMNSsVXbFNeTHwbHEYjDg5dQ6eUkxPdBSMWBeQwe2Mw+xG
-----END CERTIFICATE-----
BAD_SSL_CERT
end
def cert_chain
[ master_issued_by_master_ca, master_ca, root_ca ].map do |pem|
OpenSSL::X509::Certificate.new(pem)
end
end
def cert_chain_agent_ca
[ master_issued_by_agent_ca, agent_ca, root_ca ].map do |pem|
OpenSSL::X509::Certificate.new(pem)
end
end
def cert_chain_in_callback_order
cert_chain.reverse
end
let :authz_error_prefix do
"The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file. "
end
let :expected_authz_error_msg do
authz_ca_certs = ssl_configuration.ca_auth_certificates
msg = authz_error_prefix
msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} "
msg << "Peer Chain: #{cert_chain.collect {|c| c.subject}.join(' => ')}"
msg
end
let :root_ca_cert do
OpenSSL::X509::Certificate.new(root_ca)
end
let :master_ca_cert do
OpenSSL::X509::Certificate.new(master_ca)
end
let :agent_ca_cert do
OpenSSL::X509::Certificate.new(agent_ca)
end
end